float Profile::GetSongsPossible( StepsType st, Difficulty dc ) const { int iTotalSteps = 0; // add steps high scores const vector<Song*> vSongs = SONGMAN->GetAllSongs(); for( unsigned i=0; i<vSongs.size(); i++ ) { Song* pSong = vSongs[i]; if( pSong->m_SelectionDisplay == Song::SHOW_NEVER ) continue; // skip vector<Steps*> vSteps = pSong->GetAllSteps(); for( unsigned j=0; j<vSteps.size(); j++ ) { Steps* pSteps = vSteps[j]; if( pSteps->m_StepsType != st ) continue; // skip if( pSteps->GetDifficulty() != dc ) continue; // skip iTotalSteps++; } } return (float) iTotalSteps; }
Steps* SongUtil::GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked ) { ASSERT( dc != Difficulty_Invalid ); const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); Steps *pClosest = NULL; int iClosestDistance = 999; for( unsigned i=0; i<vpSteps.size(); i++ ) // for each of the Song's Steps { Steps* pSteps = vpSteps[i]; if( pSteps->GetDifficulty() == Difficulty_Edit && dc != Difficulty_Edit ) continue; if( bIgnoreLocked && UNLOCKMAN->StepsIsLocked(pSong,pSteps) ) continue; int iDistance = abs(dc - pSteps->GetDifficulty()); if( iDistance < iClosestDistance ) { pClosest = pSteps; iClosestDistance = iDistance; } } return pClosest; }
static bool WriteDWINotesTag( RageFile &f, const Steps &out ) { if( out.GetDifficulty() == Difficulty_Edit ) return false; // not supported by DWI LOG->Trace( "Steps::WriteDWINotesTag" ); switch( out.m_StepsType ) { case StepsType_dance_single: f.Write( "#SINGLE:" ); break; case StepsType_dance_couple: f.Write( "#COUPLE:" ); break; case StepsType_dance_double: f.Write( "#DOUBLE:" ); break; case StepsType_dance_solo: f.Write( "#SOLO:" ); break; default: return false; // not a type supported by DWI } switch( out.GetDifficulty() ) { case Difficulty_Beginner: f.Write( "BEGINNER:" ); break; case Difficulty_Easy: f.Write( "BASIC:" ); break; case Difficulty_Medium: f.Write( "ANOTHER:" ); break; case Difficulty_Hard: f.Write( "MANIAC:" ); break; case Difficulty_Challenge: f.Write( "SMANIAC:" ); break; default: ASSERT(0); return false; } f.PutLine( ssprintf("%d:", out.GetMeter()) ); return true; }
const TimingData &PlayerState::GetDisplayedTiming() const { Steps *steps = GAMESTATE->m_pCurSteps[m_PlayerNumber]; if( steps == NULL ) return GAMESTATE->m_pCurSong->m_SongTiming; return *steps->GetTimingData(); }
void ScreenEditMenu::HandleScreenMessage( const ScreenMessage SM ) { if( SM == SM_RefreshSelector ) { m_Selector.RefreshAll(); RefreshNumStepsLoadedFromProfile(); } else if( SM == SM_Success && m_Selector.GetSelectedAction() == EditMenuAction_Delete ) { LOG->Trace( "Delete successful; deleting steps from memory" ); Song* pSong = GAMESTATE->m_pCurSong; Steps* pStepsToDelete = GAMESTATE->m_pCurSteps[PLAYER_1]; FOREACH_PlayerNumber(pn) { GAMESTATE->m_pCurSteps[pn].Set(NULL); } bool bSaveSong = !pStepsToDelete->WasLoadedFromProfile(); pSong->DeleteSteps( pStepsToDelete ); SONGMAN->Invalidate( pSong ); /* Only save to the main .SM file if the steps we're deleting * were loaded from it. */ if( bSaveSong ) { pSong->Save(); SCREENMAN->ZeroNextUpdate(); } SCREENMAN->SendMessageToTopScreen( SM_RefreshSelector ); }
void StepsUtil::SortStepsPointerArrayByNumPlays( vector<Steps*> &vStepsPointers, const Profile* pProfile, bool bDecending ) { // ugly... vector<Song*> vpSongs = SONGMAN->GetAllSongs(); vector<Steps*> vpAllSteps; map<Steps*,Song*> mapStepsToSong; { for( unsigned i=0; i<vpSongs.size(); i++ ) { Song* pSong = vpSongs[i]; vector<Steps*> vpSteps = pSong->GetAllSteps(); for( unsigned j=0; j<vpSteps.size(); j++ ) { Steps* pSteps = vpSteps[j]; if( pSteps->IsAutogen() ) continue; // skip vpAllSteps.push_back( pSteps ); mapStepsToSong[pSteps] = pSong; } } } ASSERT( pProfile != NULL ); for(unsigned i = 0; i < vStepsPointers.size(); ++i) { Steps* pSteps = vStepsPointers[i]; Song* pSong = mapStepsToSong[pSteps]; steps_sort_val[vStepsPointers[i]] = ssprintf("%9i", pProfile->GetStepsNumTimesPlayed(pSong,pSteps)); } stable_sort( vStepsPointers.begin(), vStepsPointers.end(), bDecending ? CompareStepsPointersBySortValueDescending : CompareStepsPointersBySortValueAscending ); steps_sort_val.clear(); }
int main( int argc, char** argv ) { Steps* s; s = new Steps( argv[1], argv[2], argv[3], 1000 ); WorkUnit* wu; while ( (wu = s->newWorkUnit()) != NULL ) { wu->print( stdout ); delete wu; } }
void Calibration::SignalPath::add_step (Scalar* function, double step) { Steps* steps = dynamic_cast<Steps*> (function); if (steps) { #ifdef _DEBUG cerr << "Calibration::SignalPath::add_step step=" << step << endl; #endif steps->add_step (step); } }
void SongUtil::SortSongPointerArrayByMeter( vector<Song*> &arraySongPointers, Difficulty dc ) { song_sort_val.clear(); for(unsigned i = 0; i < arraySongPointers.size(); ++i) { Steps* pSteps = arraySongPointers[i]->GetStepsByDifficulty( GAMESTATE->GetCurrentStyle()->m_StepsType, dc ); CString &s = song_sort_val[arraySongPointers[i]]; s = ssprintf("%03d", pSteps ? pSteps->GetMeter() : 0); if( PREFSMAN->m_bSubSortByNumSteps ) s += ssprintf("%06.0f",pSteps ? pSteps->GetRadarValues()[RADAR_NUM_TAPS_AND_HOLDS] : 0); } stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending ); }
float Profile::GetSongsActual( StepsType st, Difficulty dc ) const { float fTotalPercents = 0; // add steps high scores for( std::map<SongID,HighScoresForASong>::const_iterator i = m_SongHighScores.begin(); i != m_SongHighScores.end(); ++i ) { const SongID &id = i->first; Song* pSong = id.ToSong(); // If the Song isn't loaded on the current machine, then we can't // get radar values to compute dance points. if( pSong == NULL ) continue; if( pSong->m_SelectionDisplay == Song::SHOW_NEVER ) continue; // skip const HighScoresForASong &hsfas = i->second; for( std::map<StepsID,HighScoresForASteps>::const_iterator j = hsfas.m_StepsHighScores.begin(); j != hsfas.m_StepsHighScores.end(); ++j ) { const StepsID &id = j->first; Steps* pSteps = id.ToSteps( pSong, true ); // If the Steps isn't loaded on the current machine, then we can't // get radar values to compute dance points. if( pSteps == NULL ) continue; if( pSteps->m_StepsType != st ) continue; if( pSteps->GetDifficulty() != dc ) continue; // skip const HighScoresForASteps& h = j->second; const HighScoreList& hs = h.hs; fTotalPercents += hs.GetTopScore().fPercentDP; } } return fTotalPercents; }
bool KSFLoader::LoadNoteDataFromSimfile( const std::string & cachePath, Steps &out ) { bool KIUCompliant = false; Song dummy; if (!LoadGlobalData(cachePath, dummy, KIUCompliant)) return false; Steps *notes = dummy.CreateSteps(); if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant)) { KIUCompliant = true; // yeah, reusing a variable. out.SetNoteData(notes->GetNoteData()); } delete notes; return KIUCompliant; }
void Calibration::SignalPath::add_step (Scalar* function, const MJD& mjd) { Steps* steps = dynamic_cast<Steps*> (function); if (!steps) throw Error (InvalidState, "Calibration::SignalPath::add_step", "function is not a Steps"); MJD zero; if (convert.get_reference_epoch() == zero) convert.set_reference_epoch ( mjd ); time.set_value (mjd); double step = convert.get_value(); steps->add_step (step); }
bool KSFLoader::LoadFromDir( const std::string &sDir, Song &out ) { LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() ); vector<std::string> arrayKSFFileNames; GetDirListing( sDir + "*.ksf", arrayKSFFileNames ); // We shouldn't have been called to begin with if there were no KSFs. ASSERT( arrayKSFFileNames.size() != 0 ); bool bKIUCompliant = false; /* With Split Timing, there has to be a backup Song Timing in case * anything goes wrong. As these files are kept in alphabetical * order (hopefully), it is best to use the LAST file for timing * purposes, for that is the "normal", or easiest difficulty. * Usually. */ // Nevermind, kiu compilancy is screwing things up: // IE, I have two simfiles, oh wich each have four ksf files, the first one has // the first ksf with directmove timing changes, and the rest are not, everything // goes fine. In the other hand I have my second simfile with the first ksf file // without directmove timing changes and the rest have changes, changes are not // loaded due to kiucompilancy in the first ksf file. // About the "normal" thing, my simfiles' ksfs uses non-standard naming so // the last chart is usually nightmare or normal, I use easy and normal // indistinctly for SM so it shouldn't matter, I use piu fiesta/ex naming // for directmove though, and we're just gathering basic info anyway, and // most of the time all the KSF files have the same info in the #TITLE:; section unsigned files = arrayKSFFileNames.size(); std::string dir = out.GetSongDir(); if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) ) return false; out.m_sSongFileName = dir + arrayKSFFileNames[files - 1]; // load the Steps from the rest of the KSF files for( unsigned i=0; i<files; i++ ) { Steps* pNewNotes = out.CreateSteps(); if( !LoadFromKSFFile(dir + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) ) { delete pNewNotes; continue; } pNewNotes->SetFilename(dir + arrayKSFFileNames[i]); out.AddSteps( pNewNotes ); } return true; }
Steps* SongUtil::GetStepsByDifficulty( const Song *pSong, StepsType st, Difficulty dc, bool bIncludeAutoGen ) { const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); for( unsigned i=0; i<vpSteps.size(); i++ ) // for each of the Song's Steps { Steps* pSteps = vpSteps[i]; if( dc != Difficulty_Invalid && dc != pSteps->GetDifficulty() ) continue; if( !bIncludeAutoGen && pSteps->IsAutogen() ) continue; return pSteps; } return NULL; }
Steps* SongUtil::GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow, int iMeterHigh ) { const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); for( unsigned i=0; i<vpSteps.size(); i++ ) // for each of the Song's Steps { Steps* pSteps = vpSteps[i]; if( iMeterLow > pSteps->GetMeter() ) continue; if( iMeterHigh < pSteps->GetMeter() ) continue; return pSteps; } return NULL; }
FOREACH_ENUM( Difficulty, dc ) { if( dc == Difficulty_Edit ) continue; vector<Steps*> vSteps; SongUtil::GetSteps( &p, vSteps, st, dc ); StepsUtil::SortNotesArrayByDifficulty( vSteps ); for( unsigned k=1; k<vSteps.size(); k++ ) { Steps* pSteps = vSteps[k]; Difficulty dc2 = min( (Difficulty)(dc+1), Difficulty_Challenge ); pSteps->SetDifficulty( dc2 ); } }
SongCreditDisplay::SongCreditDisplay() { if( GAMESTATE->IsCourseMode() ) return; this->LoadFromFont( THEME->GetPathF("SongCreditDisplay","text") ); Song* pSong = GAMESTATE->m_pCurSong; ASSERT( pSong ); CString s = pSong->GetFullDisplayTitle() + "\n" + pSong->GetDisplayArtist() + "\n"; if( !pSong->m_sCredit.empty() ) s += pSong->m_sCredit + "\n"; // use a vector and not a set so that ordering is maintained vector<Steps*> vpStepsToShow; FOREACH_PlayerNumber( p ) { if( !GAMESTATE->IsHumanPlayer(p) ) continue; // skip Steps* pSteps = GAMESTATE->m_pCurSteps[p]; bool bAlreadyAdded = find( vpStepsToShow.begin(), vpStepsToShow.end(), pSteps ) != vpStepsToShow.end(); if( !bAlreadyAdded ) vpStepsToShow.push_back( pSteps ); } for( unsigned i=0; i<vpStepsToShow.size(); i++ ) { Steps* pSteps = vpStepsToShow[i]; CString sDifficulty = DifficultyToThemedString( pSteps->GetDifficulty() ); // HACK: reset capitalization sDifficulty.MakeLower(); sDifficulty = Capitalize( sDifficulty ); s += sDifficulty + " steps: " + pSteps->GetDescription() + "\n"; } // erase the last newline s.erase( s.end()-1 ); this->SetText( s ); }
void Calibration::SignalPath::fix_last_step (Scalar* function) { Steps* steps = dynamic_cast<Steps*> (function); if (steps && step_after_cal) { int nstep = steps->get_nstep(); if (nstep > 0) { time.set_value ( max_epoch ); if (convert.get_value() < steps->get_step(nstep-1)) { #ifdef _DEBUG cerr << "fix_last_step: removing step " << nstep-1 << endl; #endif steps->remove_step(nstep-1); } } } }
void Calibration::SignalPath::offset_steps (Scalar* function, double offset) { Steps* steps = dynamic_cast<Steps*> (function); if (!steps) return; #ifdef _DEBUG cerr << "Calibration::SignalPath::offset_steps offset=" << offset << " "; #endif for (unsigned i=0; i < steps->get_nstep(); i++) { steps->set_step( i, steps->get_step(i) + offset ); #ifdef _DEBUG cerr << i << "=" << steps->get_step(i) << " "; #endif } #ifdef _DEBUG cerr << endl; #endif }
void Calibration::SignalPath::set_min_step (Scalar* function, double step) { Steps* steps = dynamic_cast<Steps*> (function); if (!steps) return; if (!steps->get_nstep()) { #ifdef _DEBUG cerr << "Calibration::SignalPath::set_min_step add step[0]=" << step << endl; #endif steps->add_step( step ); } else if (step < steps->get_step(0)) { #ifdef _DEBUG cerr << "Calibration::SignalPath::set_min_step set step[0]=" << step << endl; #endif if (step_after_cal) steps->add_step( step); else steps->set_step( 0, step ); } }
/* This data is added to each player profile, and to the machine profile per-player. */ void AddPlayerStatsToProfile( Profile *pProfile, const StageStats &ss, PlayerNumber pn ) { ss.AssertValid( pn ); CHECKPOINT; StyleID sID; sID.FromStyle( ss.m_pStyle ); ASSERT( (int) ss.m_vpPlayedSongs.size() == ss.m_player[pn].m_iStepsPlayed ); for( int i=0; i<ss.m_player[pn].m_iStepsPlayed; i++ ) { Steps *pSteps = ss.m_player[pn].m_vpPossibleSteps[i]; pProfile->m_iNumSongsPlayedByPlayMode[ss.m_playMode]++; pProfile->m_iNumSongsPlayedByStyle[sID] ++; pProfile->m_iNumSongsPlayedByDifficulty[pSteps->GetDifficulty()] ++; int iMeter = clamp( pSteps->GetMeter(), 0, MAX_METER ); pProfile->m_iNumSongsPlayedByMeter[iMeter] ++; } pProfile->m_iTotalDancePoints += ss.m_player[pn].m_iActualDancePoints; if( ss.m_Stage == Stage_Extra1 || ss.m_Stage == Stage_Extra2 ) { if( ss.m_player[pn].m_bFailed ) ++pProfile->m_iNumExtraStagesFailed; else ++pProfile->m_iNumExtraStagesPassed; } // If you fail in a course, you passed all but the final song. // FIXME: Not true. If playing with 2 players, one player could have failed earlier. if( !ss.m_player[pn].m_bFailed ) { pProfile->m_iNumStagesPassedByPlayMode[ss.m_playMode] ++; pProfile->m_iNumStagesPassedByGrade[ss.m_player[pn].GetGrade()] ++; } }
void SMLoader::LoadFromSMTokens( CString sStepsType, CString sDescription, CString sDifficulty, CString sMeter, CString sRadarValues, CString sNoteData, CString sAttackData, Steps &out ) { TrimLeft(sStepsType); TrimRight(sStepsType); TrimLeft(sDescription); TrimRight(sDescription); TrimLeft(sDifficulty); TrimRight(sDifficulty); // LOG->Trace( "Steps::LoadFromSMTokens()" ); out.m_StepsType = GameManager::StringToStepsType(sStepsType); out.SetDescription(sDescription); out.SetDifficulty(StringToDifficulty( sDifficulty )); // HACK: We used to store SMANIAC as DIFFICULTY_HARD with special description. // Now, it has its own DIFFICULTY_CHALLENGE if( sDescription.CompareNoCase("smaniac") == 0 ) out.SetDifficulty( DIFFICULTY_CHALLENGE ); // HACK: We used to store CHALLENGE as DIFFICULTY_HARD with special description. // Now, it has its own DIFFICULTY_CHALLENGE if( sDescription.CompareNoCase("challenge") == 0 ) out.SetDifficulty( DIFFICULTY_CHALLENGE ); out.SetMeter(atoi(sMeter)); CStringArray saValues; split( sRadarValues, ",", saValues, true ); if( saValues.size() == NUM_RADAR_CATEGORIES ) { RadarValues v; FOREACH_RadarCategory(rc) v[rc] = strtof( saValues[rc], NULL ); out.SetRadarValues( v ); } out.SetSMNoteData(sNoteData, sAttackData); out.TidyUpData(); }
int main(int argc, char **argv) { const char *filename = nullptr; unsigned long max_stages = 1; for (int i = 1; i < argc; ++i) { std::string arg(argv[i]); if (arg == "--max-stages") { if (++i == argc) { usage(); std::exit(1); } char *endp; max_stages = strtoul(argv[i], &endp, 10); if (*endp != '\0' || max_stages > std::numeric_limits<unsigned>::max()) { usage(); std::exit(1); } } else if (arg == "--help") { usage(); std::exit(0); } else { if (filename) { usage(); std::exit(1); } filename = argv[i]; } } const auto map = filename ? read_map(filename) : read_map(std::cin); Steps steps = map_generator::generate(map, max_stages); steps.print(std::cout); CodeCost cost = steps.compute_cost(); std::cerr << "Number of bytes: " << cost.bytes << '\n'; std::cerr << "Number of cycles: " << cost.cycles << '\n'; return 0; }
/* Add a list of difficulties/edits to the given row/handler. */ void ScreenOptionsMaster::SetStep( OptionRowData &row, OptionRowHandler &hand ) { hand.type = ROW_STEP; row.bOneChoiceForAllPlayers = false; // fill in difficulty names if( GAMESTATE->m_bEditing ) { row.choices.push_back( "" ); hand.ListEntries.push_back( ModeChoice() ); } else if( GAMESTATE->IsCourseMode() ) // playing a course { row.bOneChoiceForAllPlayers = PREFSMAN->m_bLockCourseDifficulties; vector<Trail*> vTrails; GAMESTATE->m_pCurCourse->GetTrails( vTrails, GAMESTATE->GetCurrentStyle()->m_StepsType ); ModeChoice mc; for( unsigned i=0; i<vTrails.size(); i++ ) { row.choices.push_back( CourseDifficultyToThemedString(vTrails[i]->m_CourseDifficulty) ); mc.m_pTrail = vTrails[i]; hand.ListEntries.push_back( mc ); } } else // !GAMESTATE->IsCourseMode(), playing a song { vector<Steps*> vSteps; GAMESTATE->m_pCurSong->GetSteps( vSteps, GAMESTATE->GetCurrentStyle()->m_StepsType ); StepsUtil::SortNotesArrayByDifficulty( vSteps ); ModeChoice mc; for( unsigned i=0; i<vSteps.size(); i++ ) { Steps* pSteps = vSteps[i]; CString s; if( pSteps->GetDifficulty() == DIFFICULTY_EDIT ) s = pSteps->GetDescription(); else s = DifficultyToThemedString( pSteps->GetDifficulty() ); s += ssprintf( " (%d)", pSteps->GetMeter() ); row.choices.push_back( s ); mc.m_pSteps = pSteps; mc.m_dc = pSteps->GetDifficulty(); hand.ListEntries.push_back( mc ); } } }
void SongUtil::GetSteps( const Song *pSong, vector<Steps*>& arrayAddTo, StepsType st, Difficulty dc, int iMeterLow, int iMeterHigh, const RString &sDescription, bool bIncludeAutoGen, unsigned uHash, int iMaxToGet ) { if( !iMaxToGet ) return; const vector<Steps*> &vpSteps = st == StepsType_Invalid ? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); for( unsigned i=0; i<vpSteps.size(); i++ ) // for each of the Song's Steps { Steps* pSteps = vpSteps[i]; if( dc != Difficulty_Invalid && dc != pSteps->GetDifficulty() ) continue; if( iMeterLow != -1 && iMeterLow > pSteps->GetMeter() ) continue; if( iMeterHigh != -1 && iMeterHigh < pSteps->GetMeter() ) continue; if( sDescription.size() && sDescription != pSteps->GetDescription() ) continue; if( uHash != 0 && uHash != pSteps->GetHash() ) continue; if( !bIncludeAutoGen && pSteps->IsAutogen() ) continue; arrayAddTo.push_back( pSteps ); if( iMaxToGet != -1 ) { --iMaxToGet; if( !iMaxToGet ) break; } } }
void NotesWriterSM::WriteSMNotesTag( const Steps &in, RageFile &f, bool bSavingCache ) { f.PutLine( "" ); f.PutLine( ssprintf( "//---------------%s - %s----------------", GameManager::StepsTypeToString(in.m_StepsType).c_str(), in.GetDescription().c_str() ) ); f.PutLine( "#NOTES:" ); f.PutLine( ssprintf( " %s:", GameManager::StepsTypeToString(in.m_StepsType).c_str() ) ); f.PutLine( ssprintf( " %s:", in.GetDescription().c_str() ) ); f.PutLine( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) ); f.PutLine( ssprintf( " %d:", in.GetMeter() ) ); int MaxRadar = bSavingCache? NUM_RADAR_CATEGORIES:5; CStringArray asRadarValues; for( int r=0; r < MaxRadar; r++ ) asRadarValues.push_back( ssprintf("%.3f", in.GetRadarValues()[r]) ); /* Don't append a newline here; it's added in NoteDataUtil::GetSMNoteDataString. * If we add it here, then every time we write unmodified data we'll add an extra * newline and they'll accumulate. */ f.Write( ssprintf( " %s:", join(",",asRadarValues).c_str() ) ); CString sNoteData; CString sAttackData; in.GetSMNoteData( sNoteData, sAttackData ); vector<CString> lines; split( sNoteData, "\n", lines, false ); WriteLineList( f, lines, true, true ); if( sAttackData.empty() ) f.PutLine( ";" ); else { f.PutLine( ":" ); lines.clear(); split( sAttackData, "\n", lines, false ); WriteLineList( f, lines, true, true ); f.PutLine( ";" ); } }
void SMLoader::LoadFromTokens( RString sStepsType, RString sDescription, RString sDifficulty, RString sMeter, RString sRadarValues, RString sNoteData, Steps &out ) { // we're loading from disk, so this is by definition already saved: out.SetSavedToDisk( true ); Trim( sStepsType ); Trim( sDescription ); Trim( sDifficulty ); Trim( sNoteData ); // LOG->Trace( "Steps::LoadFromTokens(), %s", sStepsType.c_str() ); // backwards compatibility hacks: // HACK: We eliminated "ez2-single-hard", but we should still handle it. if( sStepsType == "ez2-single-hard" ) sStepsType = "ez2-single"; // HACK: "para-single" used to be called just "para" if( sStepsType == "para" ) sStepsType = "para-single"; out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType ); out.m_StepsTypeStr = sStepsType; out.SetDescription( sDescription ); out.SetCredit( sDescription ); // this is often used for both. out.SetChartName(sDescription); // yeah, one more for good measure. out.SetDifficulty( OldStyleStringToDifficulty(sDifficulty) ); // Handle hacks that originated back when StepMania didn't have // Difficulty_Challenge. (At least v1.64, possibly v3.0 final...) if( out.GetDifficulty() == Difficulty_Hard ) { // HACK: SMANIAC used to be Difficulty_Hard with a special description. if( sDescription.CompareNoCase("smaniac") == 0 ) out.SetDifficulty( Difficulty_Challenge ); // HACK: CHALLENGE used to be Difficulty_Hard with a special description. if( sDescription.CompareNoCase("challenge") == 0 ) out.SetDifficulty( Difficulty_Challenge ); } if( sMeter.empty() ) { // some simfiles (e.g. X-SPECIALs from Zenius-I-Vanisher) don't // have a meter on certain steps. Make the meter 1 in these instances. sMeter = "1"; } out.SetMeter( StringToInt(sMeter) ); out.SetSMNoteData( sNoteData ); out.TidyUpData(); }
/* For visibility testing: if bAbsolute is false, random modifiers must return * the minimum possible scroll speed. */ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute ) { // Default values that are returned if boomerang is off. fPeakYOffsetOut = FLT_MAX; bIsPastPeakOut = true; float fYOffset = 0; const SongPosition &position = pPlayerState->GetDisplayedPosition(); float fSongBeat = position.m_fSongBeatVisible; Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber]; /* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or * entirely time spacing (respectively). Occasionally, we tween between them. */ if( curr_options->m_fTimeSpacing != 1.0f ) { if( GAMESTATE->m_bInStepEditor ) { // Use constant spacing in step editor fYOffset = fNoteBeat - fSongBeat; } else { fYOffset = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat); fYOffset *= pCurSteps->GetTimingData()->GetDisplayedSpeedPercent( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible ); } fYOffset *= 1 - curr_options->m_fTimeSpacing; } if( curr_options->m_fTimeSpacing != 0.0f ) { float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible; float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat); float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fBPM = curr_options->m_fScrollBPM; float fBPS = fBPM/60.f / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS; fYOffset += fYOffsetTimeSpacing * curr_options->m_fTimeSpacing; } // TODO: If we allow noteskins to have metricable row spacing // (per issue 24), edit this to reflect that. -aj fYOffset *= ARROW_SPACING; // Factor in scroll speed float fScrollSpeed = curr_options->m_fScrollSpeed; if(curr_options->m_fMaxScrollBPM != 0) { fScrollSpeed= curr_options->m_fMaxScrollBPM / (pPlayerState->m_fReadBPM * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); } // don't mess with the arrows after they've crossed 0 if( fYOffset < 0 ) { return fYOffset * fScrollSpeed; } const float* fAccels = curr_options->m_fAccels; //const float* fEffects = curr_options->m_fEffects; float fYAdjust = 0; // fill this in depending on PlayerOptions if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 ) { float fEffectHeight = GetNoteFieldHeight(); float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); float fAccelYAdjust = fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen. CLAMP( fAccelYAdjust, BOOST_MOD_MIN_CLAMP, BOOST_MOD_MAX_CLAMP ); fYAdjust += fAccelYAdjust; } if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 ) { float fEffectHeight = GetNoteFieldHeight(); float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f ); float fNewYOffset = fYOffset * fScale; float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST CLAMP( fBrakeYAdjust, BRAKE_MOD_MIN_CLAMP, BRAKE_MOD_MAX_CLAMP ); fYAdjust += fBrakeYAdjust; } if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 ) fYAdjust += fAccels[PlayerOptions::ACCEL_WAVE] * WAVE_MOD_MAGNITUDE *RageFastSin( fYOffset/WAVE_MOD_HEIGHT ); fYOffset += fYAdjust; // Factor in boomerang if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 ) { float fPeakAtYOffset = SCREEN_HEIGHT * BOOMERANG_PEAK_PERCENTAGE; // zero point of boomerang function fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset; bIsPastPeakOut = fYOffset < fPeakAtYOffset; fYOffset = (-1*fYOffset*fYOffset/SCREEN_HEIGHT) + 1.5f*fYOffset; } if( curr_options->m_fRandomSpeed > 0 && !bAbsolute ) { // Generate a deterministically "random" speed for each arrow. unsigned seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100); for( int i = 0; i < 3; ++i ) seed = ((seed * 1664525u) + 1013904223u) & 0xFFFFFFFF; float fRandom = seed / 4294967296.0f; /* Random speed always increases speed: a random speed of 10 indicates * [1,11]. This keeps it consistent with other mods: 0 means no effect. */ fScrollSpeed *= SCALE( fRandom, 0.0f, 1.0f, 1.0f, curr_options->m_fRandomSpeed + 1.0f ); } if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 ) { // TODO: Don't index by PlayerNumber. PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber]; float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY), EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH, EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH ); fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], EXPAND_SPEED_SCALE_FROM_LOW, EXPAND_SPEED_SCALE_FROM_HIGH, EXPAND_SPEED_SCALE_TO_LOW, fExpandMultiplier ); } fYOffset *= fScrollSpeed; fPeakYOffsetOut *= fScrollSpeed; return fYOffset; }
Steps(const Steps& s) : std::vector < Step* >(s) { for(const_iterator it = s.begin(); it != s.end(); ++it) push_back(new Step(**it)); }
static bool LoadFromKSFFile( const std::string &sPath, Steps &out, Song &song, bool bKIUCompliant ) { using std::max; LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath, false ) ) // don't unescape { LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); return false; } // this is the value we read for TICKCOUNT int iTickCount = -1; // used to adapt weird tickcounts //float fScrollRatio = 1.0f; -- uncomment when ready to use. vector<std::string> vNoteRows; // According to Aldo_MX, there is a default BPM and it's 60. -aj bool bDoublesChart = false; TimingData stepsTiming; float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; for( unsigned i=0; i<msd.GetNumValues(); i++ ) { const MsdFile::value_t &sParams = msd.GetValue( i ); std::string sValueName = Rage::make_upper(sParams[0]); /* handle the data...well, not this data: not related to steps. * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */ if (sValueName=="TITLE" || Rage::ends_with(sValueName, "INTRO") || Rage::ends_with(sValueName, "FILE") ) { } else if( sValueName=="BPM" ) { BPM1 = StringToFloat(sParams[1]); stepsTiming.AddSegment( BPMSegment(0, BPM1) ); } else if( sValueName=="BPM2" ) { if (bKIUCompliant) { BPM2 = StringToFloat( sParams[1] ); } else { // LOG an error. } } else if( sValueName=="BPM3" ) { if (bKIUCompliant) { BPM3 = StringToFloat( sParams[1] ); } else { // LOG an error. } } else if( sValueName=="BUNKI" ) { if (bKIUCompliant) { BPMPos2 = StringToFloat( sParams[1] ) / 100.0f; } else { // LOG an error. } } else if( sValueName=="BUNKI2" ) { if (bKIUCompliant) { BPMPos3 = StringToFloat( sParams[1] ) / 100.0f; } else { // LOG an error. } } else if( sValueName=="STARTTIME" ) { SMGap1 = -StringToFloat( sParams[1] )/100; stepsTiming.set_offset(SMGap1); } // This is currently required for more accurate KIU BPM changes. else if( sValueName=="STARTTIME2" ) { if (bKIUCompliant) { SMGap2 = -StringToFloat( sParams[1] )/100; } else { // LOG an error. } } else if ( sValueName=="STARTTIME3" ) { // STARTTIME3 only ensures this is a KIU compliant simfile. bKIUCompliant = true; } else if( sValueName=="TICKCOUNT" ) { iTickCount = StringToInt( sParams[1] ); if( iTickCount <= 0 ) { LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount ); return false; } stepsTiming.AddSegment( TickcountSegment(0, iTickCount)); } else if( sValueName=="DIFFICULTY" ) { out.SetMeter( max(StringToInt(sParams[1]), 1) ); } // new cases from Aldo_MX's fork: else if( sValueName=="PLAYER" ) { std::string sPlayer = Rage::make_lower(sParams[1]); if( sPlayer.find( "double" ) != string::npos ) { bDoublesChart = true; } } // This should always be last. else if( sValueName=="STEP" ) { std::string theSteps = Rage::trim_left(sParams[1]); auto toDump = Rage::split(theSteps, "\n", Rage::EmptyEntries::skip); vNoteRows.insert(vNoteRows.end(), std::make_move_iterator(toDump.begin()), std::make_move_iterator(toDump.end())); } } if( iTickCount == -1 ) { iTickCount = 4; LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount ); } // Prepare BPM stuff already if the file uses KSF syntax. if( bKIUCompliant ) { if( BPM2 > 0 && BPMPos2 > 0 ) { HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 ); } if( BPM3 > 0 && BPMPos3 > 0 ) { HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 ); } } NoteData notedata; // read it into here { std::string sDir, sFName, sExt; splitpath( sPath, sDir, sFName, sExt ); sFName = Rage::make_lower(sFName); out.SetDescription(sFName); // Check another before anything else... is this okay? -DaisuMaster if( sFName.find("another") != string::npos ) { out.SetDifficulty( Difficulty_Edit ); if( !out.GetMeter() ) out.SetMeter( 25 ); } else if(sFName.find("wild") != string::npos || sFName.find("wd") != string::npos || sFName.find("crazy+") != string::npos || sFName.find("cz+") != string::npos || sFName.find("hardcore") != string::npos ) { out.SetDifficulty( Difficulty_Challenge ); if( !out.GetMeter() ) out.SetMeter( 20 ); } else if(sFName.find("crazy") != string::npos || sFName.find("cz") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("nm") != string::npos || sFName.find("crazydouble") != string::npos ) { out.SetDifficulty( Difficulty_Hard ); if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR. } else if(sFName.find("hard") != string::npos || sFName.find("hd") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("fs") != string::npos || sFName.find("double") != string::npos ) { out.SetDifficulty( Difficulty_Medium ); if( !out.GetMeter() ) out.SetMeter( 8 ); } else if(sFName.find("easy") != string::npos || sFName.find("ez") != string::npos || sFName.find("normal") != string::npos ) { // I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster out.SetDifficulty( Difficulty_Easy ); if( !out.GetMeter() ) out.SetMeter( 4 ); } else if(sFName.find("beginner") != string::npos || sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) { out.SetDifficulty( Difficulty_Beginner ); if( !out.GetMeter() ) out.SetMeter( 4 ); } else { out.SetDifficulty( Difficulty_Hard ); if( !out.GetMeter() ) out.SetMeter( 10 ); } out.m_StepsType = StepsType_pump_single; // Check for "halfdouble" before "double". if(sFName.find("halfdouble") != string::npos || sFName.find("half-double") != string::npos || sFName.find("h_double") != string::npos || sFName.find("hdb") != string::npos ) out.m_StepsType = StepsType_pump_halfdouble; // Handle bDoublesChart from above as well. -aj else if(sFName.find("double") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("db") != string::npos || sFName.find("nm") != string::npos || sFName.find("fs") != string::npos || bDoublesChart ) out.m_StepsType = StepsType_pump_double; else if( sFName.find("_1") != string::npos ) out.m_StepsType = StepsType_pump_single; else if( sFName.find("_2") != string::npos ) out.m_StepsType = StepsType_pump_couple; } switch( out.m_StepsType ) { case StepsType_pump_single: notedata.SetNumTracks( 5 ); break; case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break; case StepsType_pump_double: notedata.SetNumTracks( 10 ); break; case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this? case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break; default: FAIL_M( fmt::sprintf("%i", out.m_StepsType) ); } int t = 0; int iHoldStartRow[13]; for( t=0; t<13; t++ ) iHoldStartRow[t] = -1; bool bTickChangeNeeded = false; int newTick = -1; float fCurBeat = 0.0f; float prevBeat = 0.0f; // Used for hold tails. for (auto &sRowString: vNoteRows) { sRowString = Rage::trim_right(sRowString, "\r\n"); if( sRowString == "" ) { continue; // skip } // All 2s indicates the end of the song. if( sRowString == "2222222222222" ) { // Finish any holds that didn't get...well, finished. for( t=0; t < notedata.GetNumTracks(); t++ ) { if( iHoldStartRow[t] != -1 ) // this ends the hold { if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) ) { notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); } else { notedata.AddHoldNote(t, iHoldStartRow[t], BeatToNoteRow(prevBeat), TAP_ORIGINAL_HOLD_HEAD ); } } } /* have this row be the last moment in the song, unless * a future step ends later. */ //float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat); //if (curTime > song.GetSpecifiedLastSecond()) //{ // song.SetSpecifiedLastSecond(curTime); //} song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 ); break; } else if( Rage::starts_with(sRowString, "|") ) { /* if (bKIUCompliant) { // Log an error, ignore the line. continue; } */ // gotta do something tricky here: if the bpm is below one then a couple of calculations // for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will // be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll // segment will then be added based on approximations. -DaisuMaster // eh better do it considering the tickcount (high tickcounts) // I'm making some experiments, please spare me... //continue; std::string temp = sRowString.substr(2,sRowString.size()-3); float numTemp = StringToFloat(temp); if (Rage::starts_with(sRowString, "|T")) { // duh iTickCount = static_cast<int>(numTemp); // I have been owned by the man -DaisuMaster stepsTiming.SetTickcountAtBeat( fCurBeat, Rage::clamp(iTickCount, 0, ROWS_PER_BEAT) ); } else if (Rage::starts_with(sRowString, "|B")) { // BPM stepsTiming.SetBPMAtBeat( fCurBeat, numTemp ); } else if (Rage::starts_with(sRowString, "|E")) { // DelayBeat float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount; fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) ); stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); } else if (Rage::starts_with(sRowString, "|D")) { // Delays float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) ); fCurDelay += numTemp / 1000; stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); } else if (Rage::starts_with(sRowString, "|M") || Rage::starts_with(sRowString, "|C")) { // multipliers/combo ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) ); stepsTiming.AddSegment( seg ); } else if (Rage::starts_with(sRowString, "|S")) { // speed segments } else if (Rage::starts_with(sRowString, "|F")) { // fakes } else if (Rage::starts_with(sRowString, "|X")) { // scroll segments ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp ); stepsTiming.AddSegment( seg ); //return true; } continue; } // Half-doubles is offset; "0011111100000". if( out.m_StepsType == StepsType_pump_halfdouble ) sRowString.erase( 0, 2 ); // Update TICKCOUNT for Direct Move files. if( bTickChangeNeeded ) { iTickCount = newTick; bTickChangeNeeded = false; } for( t=0; t < notedata.GetNumTracks(); t++ ) { if( sRowString[t] == '4' ) { /* Remember when each hold starts; ignore the middle. */ if( iHoldStartRow[t] == -1 ) iHoldStartRow[t] = BeatToNoteRow(fCurBeat); continue; } if( iHoldStartRow[t] != -1 ) // this ends the hold { int iEndRow = BeatToNoteRow(prevBeat); if( iHoldStartRow[t] == iEndRow ) notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); else { //notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD ); notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD ); } iHoldStartRow[t] = -1; } TapNote tap; switch( sRowString[t] ) { case '0': tap = TAP_EMPTY; break; case '1': tap = TAP_ORIGINAL_TAP; break; //allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster case 'M': case 'm': tap = TAP_ORIGINAL_MINE; break; case 'F': case 'f': tap = TAP_ORIGINAL_FAKE; break; case 'L': case 'l': tap = TAP_ORIGINAL_LIFT; break; default: LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.", sRowString.c_str() ); //return false; tap = TAP_EMPTY; break; } notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap); } prevBeat = fCurBeat; fCurBeat = prevBeat + 1.0f / iTickCount; } out.SetNoteData( notedata ); out.m_Timing = stepsTiming; out.TidyUpData(); out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved return true; }