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; }
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; }
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 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 ); }
/* 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 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( ";" ); } }
/* 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()] ++; } }
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; }
CString SongUtil::GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so ) { if( pSong == NULL ) return ""; switch( so ) { case SORT_PREFERRED: return ""; case SORT_GROUP: return pSong->m_sGroupName; case SORT_TITLE: case SORT_ARTIST: { CString s; switch( so ) { case SORT_TITLE: s = pSong->GetTranslitMainTitle(); break; case SORT_ARTIST: s = pSong->GetTranslitArtist(); break; default: ASSERT(0); } s = MakeSortString(s); // resulting string will be uppercase if( s.empty() ) return ""; else if( s[0] >= '0' && s[0] <= '9' ) return "NUM"; else if( s[0] < 'A' || s[0] > 'Z') return "OTHER"; else return s.Left(1); } case SORT_BPM: { const int iBPMGroupSize = 20; DisplayBpms bpms; pSong->GetDisplayBpms( bpms ); int iMaxBPM = (int)bpms.GetMax(); iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1; return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM); } case SORT_MOST_PLAYED: return ""; case SORT_GRADE: { int iCounts[NUM_GRADES]; PROFILEMAN->GetMachineProfile()->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts ); for( int i=GRADE_TIER_1; i<NUM_GRADES; ++i ) { Grade g = (Grade)i; if( iCounts[i] > 0 ) return ssprintf( "%4s x %d", GradeToThemedString(g).c_str(), iCounts[i] ); } return GradeToThemedString( GRADE_NO_DATA ); } case SORT_EASY_METER: { Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_EASY); if( pSteps ) return ssprintf("%02d", pSteps->GetMeter() ); return "N/A"; } case SORT_MEDIUM_METER: { Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_MEDIUM); if( pSteps ) return ssprintf("%02d", pSteps->GetMeter() ); return "N/A"; } case SORT_HARD_METER: { Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_HARD); if( pSteps ) return ssprintf("%02d", pSteps->GetMeter() ); return "N/A"; } case SORT_CHALLENGE_METER: { Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_CHALLENGE); if( pSteps ) return ssprintf("%02d", pSteps->GetMeter() ); return "N/A"; } case SORT_SORT_MENU: case SORT_MODE_MENU: return ""; case SORT_ALL_COURSES: case SORT_NONSTOP_COURSES: case SORT_ONI_COURSES: case SORT_ENDLESS_COURSES: default: ASSERT(0); return ""; } }
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song ) { LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath ) ) RageException::Throw( "Error opening file '%s'.", sPath.c_str() ); int iTickCount = -1; // this is the value we read for TICKCOUNT CStringArray asRows; for( unsigned i=0; i<msd.GetNumValues(); i++ ) { const MsdFile::value_t &sParams = msd.GetValue(i); CString sValueName = sParams[0]; // handle the data if( 0==stricmp(sValueName,"TICKCOUNT") ) iTickCount = atoi(sParams[1]); else if( 0==stricmp(sValueName,"STEP") ) { CString step = sParams[1]; TrimLeft(step); split( step, "\n", asRows, true ); } else if( 0==stricmp(sValueName,"DIFFICULTY") ) out.SetMeter(atoi(sParams[1])); } if( iTickCount == -1 ) { iTickCount = 2; LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount ); } NoteData notedata; // read it into here { CString sDir, sFName, sExt; splitpath( sPath, sDir, sFName, sExt ); sFName.MakeLower(); out.SetDescription(sFName); if( sFName.Find("crazy")!=-1 ) { out.SetDifficulty(DIFFICULTY_HARD); if(!out.GetMeter()) out.SetMeter(8); } else if( sFName.Find("hard")!=-1 ) { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } else if( sFName.Find("easy")!=-1 ) { out.SetDifficulty(DIFFICULTY_EASY); if(!out.GetMeter()) out.SetMeter(2); } else { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } notedata.SetNumTracks( 5 ); out.m_StepsType = STEPS_TYPE_PUMP_SINGLE; /* Check for "halfdouble" before "double". */ if( sFName.Find("halfdouble") != -1 || sFName.Find("h_double") != -1 ) { notedata.SetNumTracks( 6 ); out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE; } else if( sFName.Find("double") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE; } else if( sFName.Find("_2") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_COUPLE; } } int iHoldStartRow[13]; int t; for( t=0; t<13; t++ ) iHoldStartRow[t] = -1; for( unsigned r=0; r<asRows.size(); r++ ) { CString& sRowString = asRows[r]; StripCrnl( sRowString ); if( sRowString == "" ) continue; // skip /* All 2s indicates the end of the song. */ if( sRowString == "2222222222222" ) break; if(sRowString.size() != 13) { LOG->Warn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored", sPath.c_str(), sRowString.c_str()); return false; } /* Half-doubles is offset; "0011111100000". */ if( out.m_StepsType == STEPS_TYPE_PUMP_HALFDOUBLE ) sRowString.erase( 0, 2 ); // the length of a note in a row depends on TICKCOUNT float fBeatThisRow = r/(float)iTickCount; int row = BeatToNoteRow(fBeatThisRow); for( int t=0; t < notedata.GetNumTracks(); t++ ) { if( sRowString[t] == '4' ) { /* Remember when each hold starts; ignore the middle. */ if( iHoldStartRow[t] == -1 ) iHoldStartRow[t] = r; continue; } if( iHoldStartRow[t] != -1 ) // this ends the hold { HoldNote hn ( t, /* button */ BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */ BeatToNoteRow((r-1)/(float)iTickCount) /* end */ ); notedata.AddHoldNote( hn ); iHoldStartRow[t] = -1; } TapNote tap; switch(sRowString[t]) { case '0': tap = TAP_EMPTY; break; case '1': tap = TAP_ORIGINAL_TAP; break; default: ASSERT(0); tap = TAP_EMPTY; break; } notedata.SetTapNote(t, row, tap); } } /* We need to remove holes where the BPM increases. */ // if( song.m_Timing.m_BPMSegments.size() > 1 ) // RemoveHoles( notedata, song ); out.SetNoteData(¬edata); out.TidyUpData(); return true; }
void NetworkSyncManager::StartRequest(short position) { if( !useSMserver ) return; if( GAMESTATE->m_bDemonstrationOrJukebox ) return; LOG->Trace("Requesting Start from Server."); m_packet.ClearPacket(); m_packet.Write1( NSCGSR ); unsigned char ctr=0; Steps * tSteps; tSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; if ((tSteps!=NULL) && (GAMESTATE->IsPlayerEnabled(PLAYER_1))) ctr = uint8_t(ctr+tSteps->GetMeter()*16); tSteps = GAMESTATE->m_pCurSteps[PLAYER_2]; if ((tSteps!=NULL) && (GAMESTATE->IsPlayerEnabled(PLAYER_2))) ctr = uint8_t(ctr+tSteps->GetMeter()); m_packet.Write1(ctr); ctr=0; tSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; if ((tSteps!=NULL) && (GAMESTATE->IsPlayerEnabled(PLAYER_1))) ctr = uint8_t(ctr + (int) tSteps->GetDifficulty()*16); tSteps = GAMESTATE->m_pCurSteps[PLAYER_2]; if ((tSteps!=NULL) && (GAMESTATE->IsPlayerEnabled(PLAYER_2))) ctr = uint8_t(ctr + (int) tSteps->GetDifficulty()); m_packet.Write1(ctr); //Notify server if this is for sync or not. ctr = char(position*16); m_packet.Write1(ctr); if (GAMESTATE->m_pCurSong != NULL) { m_packet.WriteNT(GAMESTATE->m_pCurSong->m_sMainTitle); m_packet.WriteNT(GAMESTATE->m_pCurSong->m_sSubTitle); m_packet.WriteNT(GAMESTATE->m_pCurSong->m_sArtist); } else { m_packet.WriteNT(""); m_packet.WriteNT(""); m_packet.WriteNT(""); } if (GAMESTATE->m_pCurCourse != NULL) m_packet.WriteNT(GAMESTATE->m_pCurCourse->GetFullDisplayTitle()); else m_packet.WriteNT(CString("")); //Send Player (and song) Options m_packet.WriteNT(GAMESTATE->m_SongOptions.GetString()); int players=0; FOREACH_PlayerNumber (p) { ++players; m_packet.WriteNT(GAMESTATE->m_PlayerOptions[p].GetString()); } for (int i=0; i<2-players; ++i) m_packet.WriteNT(""); //Write a NULL if no player //This needs to be reset before ScreenEvaluation could possibly be called for (int i=0; i<NETMAXPLAYERS; ++i) { m_EvalPlayerData[i].name=0; m_EvalPlayerData[i].grade=0; m_EvalPlayerData[i].score=0; m_EvalPlayerData[i].difficulty=(Difficulty)0; for (int j=0; j<NETNUMTAPSCORES; ++j) m_EvalPlayerData[i].tapScores[j] = 0; } //Block until go is recieved. //Switch to blocking mode (this is the only //way I know how to get precievably instantanious results bool dontExit=true; //Don't block if we are server. if (isLanServer) NetPlayerClient->blocking=false; else NetPlayerClient->blocking=true; //The following packet HAS to get through, so we turn blocking on for it as well //Don't block if we are serving NetPlayerClient->SendPack((char*)&m_packet.Data, m_packet.Position); LOG->Trace("Waiting for RECV"); m_packet.ClearPacket(); while (dontExit) { //Keep the server going during the loop. if (isLanServer) LANserver->ServerUpdate(); m_packet.ClearPacket(); if (NetPlayerClient->ReadPack((char *)&m_packet, NETMAXBUFFERSIZE)<1) if (!isLanServer) dontExit=false; // Also allow exit if there is a problem on the socket // Only do if we are not the server, otherwise the sync // gets hosed up due to non blocking mode. if (m_packet.Read1() == (NSServerOffset + NSCGSR)) dontExit=false; //Only allow passing on Start request. //Otherwise scoreboard updates and such will confuse us. } NetPlayerClient->blocking=false; }