From 25b13dfcdf7aab068895b988e4b54beeec031866 Mon Sep 17 00:00:00 2001 From: Mike Hawkins Date: Fri, 30 May 2008 01:09:00 +0000 Subject: [PATCH] Song Attacks modifier (course-style scripted mods in regular single play) --- stepmania/Themes/default/Languages/en.ini | 1 + stepmania/Themes/default/metrics.ini | 5 +- stepmania/src/NotesLoaderSM.cpp | 166 ++++++++++++++-------- stepmania/src/NotesWriterSM.cpp | 11 ++ stepmania/src/PlayerOptions.cpp | 7 +- stepmania/src/PlayerOptions.h | 1 + stepmania/src/ScreenGameplay.cpp | 12 +- stepmania/src/Song.h | 5 + 8 files changed, 143 insertions(+), 65 deletions(-) diff --git a/stepmania/Themes/default/Languages/en.ini b/stepmania/Themes/default/Languages/en.ini index f084ccaaef..49a04b6ff8 100644 --- a/stepmania/Themes/default/Languages/en.ini +++ b/stepmania/Themes/default/Languages/en.ini @@ -623,6 +623,7 @@ Silent=Silent Skip=Skip Skippy=Skippy Slow=Slow +SongAttacks=Song Attacks Song BGAnimation=Song BGAnimation Song Bitmap=Song Bitmap Song Movie=Song Movie diff --git a/stepmania/Themes/default/metrics.ini b/stepmania/Themes/default/metrics.ini index 7e3fcc4078..d5eaf2bbde 100644 --- a/stepmania/Themes/default/metrics.ini +++ b/stepmania/Themes/default/metrics.ini @@ -3669,10 +3669,11 @@ Mines,1="mod,nomines;name,Off" Mines,2="name,On" Mines,3="mod,mines;name,Add" Mines,4="mod,attackmines;name,AttackMines" -Attacks="2" -AttacksDefault="mod,no randomattacks" +Attacks="3" +AttacksDefault="mod,no randomattacks,no songattacks" Attacks,1="name,Off" Attacks,2="mod,randomattacks;name,RandomAttacks" +Attacks,3="mod,songattacks;name,SongAttacks" Hide="3" HideDefault="mod,no dark,no blind" Hide,1="name,Off" diff --git a/stepmania/src/NotesLoaderSM.cpp b/stepmania/src/NotesLoaderSM.cpp index 59927196a0..598fbce6b3 100644 --- a/stepmania/src/NotesLoaderSM.cpp +++ b/stepmania/src/NotesLoaderSM.cpp @@ -14,14 +14,14 @@ const int MAX_EDIT_STEPS_SIZE_BYTES = 30*1024; // 30KB static void LoadFromSMTokens( - RString sStepsType, - RString sDescription, - RString sDifficulty, - RString sMeter, - RString sRadarValues, - RString sNoteData, - Steps &out -) + RString sStepsType, + RString sDescription, + RString sDifficulty, + RString sMeter, + RString sRadarValues, + RString sNoteData, + Steps &out + ) { out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved @@ -30,7 +30,7 @@ static void LoadFromSMTokens( Trim( sDifficulty ); Trim( sNoteData ); -// LOG->Trace( "Steps::LoadFromSMTokens()" ); + // LOG->Trace( "Steps::LoadFromSMTokens()" ); out.m_StepsType = GameManager::StringToStepsType( sStepsType ); out.SetDescription( sDescription ); @@ -53,10 +53,10 @@ static void LoadFromSMTokens( RadarValues v[NUM_PLAYERS]; FOREACH_PlayerNumber( pn ) FOREACH_ENUM( RadarCategory, rc ) - v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); + v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] ); out.SetCachedRadarValues( v ); } - + out.SetSMNoteData( sNoteData ); out.TidyUpData(); @@ -110,16 +110,16 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) { // XXX: Hard to tell which file caused this. LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); + sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); continue; } const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] ); const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ); - + StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds ); -// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); + // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); out.AddStopSegment( new_seg ); } @@ -138,13 +138,13 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) if( arrayBPMChangeValues.size() != 2 ) { LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() ); + sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() ); continue; } const float fBeat = StringToFloat( arrayBPMChangeValues[0] ); const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] ); - + if( fNewBPM > 0.0f ) out.AddBPMSegment( BPMSegment(BeatToNoteRow(fBeat), fNewBPM) ); else @@ -174,13 +174,13 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) seg.m_iStartRow = BeatToNoteRow(fBeat); seg.m_iNumerator = atoi( vs2[1] ); seg.m_iDenominator = atoi( vs2[2] ); - + if( fBeat < 0 ) { LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat ); continue; } - + if( seg.m_iNumerator < 1 ) { LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator ); @@ -264,7 +264,7 @@ bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChange change.m_fStartBeat = StringToFloat( aBGChangeValues[0] ); // fall through } - + return aBGChangeValues.size() >= 2; } @@ -291,7 +291,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache // handle the data /* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically - * splitting other formats that *don't* natively support #SUBTITLE. */ + * splitting other formats that *don't* natively support #SUBTITLE. */ if( sValueName=="TITLE" ) out.m_sMainTitle = sParams[1]; @@ -355,7 +355,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache continue; out.m_fMusicLengthSeconds = StringToFloat( sParams[1] ); } - + else if( sValueName=="LASTBEATHINT" ) out.m_fSpecifiedLastBeat = StringToFloat( sParams[1] ); @@ -363,7 +363,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache ; /* ignore */ /* We calculate these. Some SMs in circulation have bogus values for - * these, so make sure we always calculate it ourself. */ + * these, so make sure we always calculate it ourself. */ else if( sValueName=="FIRSTBEAT" ) { if( bFromCache ) @@ -465,6 +465,52 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache split( sParams[1], ",", out.m_vsKeysoundFile ); } + // Attacks loaded from file + else if( sValueName=="ATTACKS" ) + { + // Build the RString vector here so we can write it to file again later + for( unsigned s=1; s < sParams.params.size(); ++s ) + out.m_sAttackString.push_back( sParams[s] ); + + Attack attack; + float end = -9999; + + for( unsigned j=1; j < sParams.params.size(); ++j ) + { + vector sBits; + split( sParams[j], "=", sBits, false ); + + // Need an identifer and a value for this to work + if( sBits.size() < 2 ) + continue; + + TrimLeft( sBits[0] ); + TrimRight( sBits[0] ); + + if( !sBits[0].CompareNoCase("TIME") ) + attack.fStartSecond = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("LEN") ) + attack.fSecsRemaining = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("END") ) + end = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("MODS") ) + { + attack.sModifiers = sBits[1]; + + if( end != -9999 ) + { + attack.fSecsRemaining = end - attack.fStartSecond; + end = -9999; + } + + if( attack.fSecsRemaining < 0.0f ) + attack.fSecsRemaining = 0.0f; + + out.m_Attacks.push_back( attack ); + } + } + } + else if( sValueName=="NOTES" || sValueName=="NOTES2" ) { if( iNumParams < 7 ) @@ -486,7 +532,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache out.AddSteps( pNewNotes ); } else if( sValueName=="OFFSET" || sValueName=="BPMS" || sValueName=="STOPS" || sValueName=="FREEZES" || sValueName=="TIMESIGNATURES" ) - ; + ; else LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() ); } @@ -507,7 +553,7 @@ bool SMLoader::LoadFromDir( const RString &sPath, Song &out ) } /* We should have exactly one; if we had none, we shouldn't have been - * called to begin with. */ + * called to begin with. */ ASSERT( aFileNames.size() == 1 ); return LoadFromSMFile( sPath + aFileNames[0], out ); @@ -621,26 +667,26 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath } return true; - + } void SMLoader::TidyUpData( Song &song, bool bFromCache ) { /* - * Hack: if the song has any changes at all (so it won't use a random BGA) - * and doesn't end with "-nosongbg-", add a song background BGC. Remove - * "-nosongbg-" if it exists. - * - * This way, songs that were created earlier, when we added the song BG - * at the end by default, will still behave as expected; all new songs will - * have to add an explicit song BG tag if they want it. This is really a - * formatting hack only; nothing outside of SMLoader ever sees "-nosongbg-". - */ + * Hack: if the song has any changes at all (so it won't use a random BGA) + * and doesn't end with "-nosongbg-", add a song background BGC. Remove + * "-nosongbg-" if it exists. + * + * This way, songs that were created earlier, when we added the song BG + * at the end by default, will still behave as expected; all new songs will + * have to add an explicit song BG tag if they want it. This is really a + * formatting hack only; nothing outside of SMLoader ever sees "-nosongbg-". + */ vector &bg = song.GetBackgroundChanges(BACKGROUND_LAYER_1); if( !bg.empty() ) { /* BGChanges have been sorted. On the odd chance that a BGChange exists - * with a very high beat, search the whole list. */ + * with a very high beat, search the whole list. */ bool bHasNoSongBgTag = false; for( unsigned i = 0; !bHasNoSongBgTag && i < bg.size(); ++i ) @@ -656,12 +702,12 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache ) if( !bHasNoSongBgTag ) do { /* If we're loading cache, -nosongbg- should always be in there. We must - * not call IsAFile(song.GetBackgroundPath()) when loading cache. */ + * not call IsAFile(song.GetBackgroundPath()) when loading cache. */ if( bFromCache ) break; /* If BGChanges already exist after the last beat, don't add the background - * in the middle. */ + * in the middle. */ if( !bg.empty() && bg.back().m_fStartBeat-0.0001f >= song.m_fLastBeat ) break; @@ -678,26 +724,26 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache ) } /* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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. - */ +* (c) 2001-2004 Chris Danford, Glenn Maynard +* 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. +*/ diff --git a/stepmania/src/NotesWriterSM.cpp b/stepmania/src/NotesWriterSM.cpp index b8ac5951c4..8f0cb67c03 100644 --- a/stepmania/src/NotesWriterSM.cpp +++ b/stepmania/src/NotesWriterSM.cpp @@ -168,6 +168,17 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) f.Write( "," ); } f.PutLine( ";" ); + + f.Write( "#ATTACKS:" ); + for( unsigned a=0; a < out.m_sAttackString.size(); a++ ) + { + RString sData = out.m_sAttackString[a]; + f.Write( ssprintf( "%s", sData.c_str() ) ); + + if( a != (out.m_sAttackString.size() - 1) ) + f.Write( ":" ); // Not the end, so write a divider ':' + } + f.PutLine( ";" ); } static RString JoinLineList( vector &lines ) diff --git a/stepmania/src/PlayerOptions.cpp b/stepmania/src/PlayerOptions.cpp index 7ae85d1206..4257aabaec 100644 --- a/stepmania/src/PlayerOptions.cpp +++ b/stepmania/src/PlayerOptions.cpp @@ -28,6 +28,7 @@ void PlayerOptions::Init() m_fBlind = 0; m_SpeedfBlind = 1.0f; m_fCover = 0; m_SpeedfCover = 1.0f; m_fRandAttack = 0; m_SpeedfRandAttack = 1.0f; + m_fSongAttack = 0; m_SpeedfSongAttack = 1.0f; m_bSetTiltOrSkew = false; m_fPerspectiveTilt = 0; m_SpeedfPerspectiveTilt = 1.0f; m_fSkew = 0; m_SpeedfSkew = 1.0f; @@ -63,6 +64,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds ) APPROACH( fBlind ); APPROACH( fCover ); APPROACH( fRandAttack ); + APPROACH( fSongAttack ); APPROACH( fPerspectiveTilt ); APPROACH( fSkew ); APPROACH( fPassmark ); @@ -136,7 +138,7 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const AddPart( AddTo, m_fEffects[EFFECT_DRUNK], "Drunk" ); AddPart( AddTo, m_fEffects[EFFECT_DIZZY], "Dizzy" ); - AddPart( AddTo, m_fEffects[EFFECT_CONFUSION], "Confusion" ); + AddPart( AddTo, m_fEffects[EFFECT_CONFUSION], "Confusion" ); AddPart( AddTo, m_fEffects[EFFECT_MINI], "Mini" ); AddPart( AddTo, m_fEffects[EFFECT_TINY], "Tiny" ); AddPart( AddTo, m_fEffects[EFFECT_FLIP], "Flip" ); @@ -166,6 +168,7 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const AddPart( AddTo, m_fCover, "Cover" ); AddPart( AddTo, m_fRandAttack, "RandomAttacks" ); + AddPart( AddTo, m_fSongAttack, "SongAttacks" ); AddPart( AddTo, m_fPassmark, "Passmark" ); @@ -380,6 +383,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut else if( sBit == "blind" ) SET_FLOAT( fBlind ) else if( sBit == "cover" ) SET_FLOAT( fCover ) else if( sBit == "randomattacks" ) SET_FLOAT( fRandAttack ) + else if( sBit == "songattacks" ) SET_FLOAT( fSongAttack ) else if( sBit == "passmark" ) SET_FLOAT( fPassmark ) else if( sBit == "overhead" ) { m_bSetTiltOrSkew = true; m_fSkew = 0; m_fPerspectiveTilt = 0; m_SpeedfSkew = m_SpeedfPerspectiveTilt = speed; } else if( sBit == "incoming" ) { m_bSetTiltOrSkew = true; m_fSkew = level; m_fPerspectiveTilt = -level; m_SpeedfSkew = m_SpeedfPerspectiveTilt = speed; } @@ -619,6 +623,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const COMPARE(m_fBlind); COMPARE(m_fCover); COMPARE(m_fRandAttack); + COMPARE(m_fSongAttack); COMPARE(m_fPerspectiveTilt); COMPARE(m_fSkew); COMPARE(m_sNoteSkin); diff --git a/stepmania/src/PlayerOptions.h b/stepmania/src/PlayerOptions.h index 07bfdf79ba..f42d06c028 100644 --- a/stepmania/src/PlayerOptions.h +++ b/stepmania/src/PlayerOptions.h @@ -134,6 +134,7 @@ public: float m_fBlind, m_SpeedfBlind; float m_fCover, m_SpeedfCover; // hide the background per-player--can't think of a good name float m_fRandAttack, m_SpeedfRandAttack; + float m_fSongAttack, m_SpeedfSongAttack; bool m_bSetTiltOrSkew; // true if the tilt or skew was set by FromString float m_fPerspectiveTilt, m_SpeedfPerspectiveTilt; // -1 = near, 0 = overhead, +1 = space float m_fSkew, m_SpeedfSkew; // 0 = vanish point is in center of player, 1 = vanish point is in center of screen diff --git a/stepmania/src/ScreenGameplay.cpp b/stepmania/src/ScreenGameplay.cpp index 856bd1919a..e4caa6e327 100644 --- a/stepmania/src/ScreenGameplay.cpp +++ b/stepmania/src/ScreenGameplay.cpp @@ -830,8 +830,16 @@ void ScreenGameplay::InitSongQueues() Steps *pSteps = GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ]; pi->m_vpStepsQueue.push_back( pSteps ); - AttackArray aa; - pi->m_asModifiersQueue.push_back( aa ); + if( pi->GetPlayerState()->m_PlayerOptions.GetCurrent().m_fSongAttack != 0 && + GAMESTATE->m_pCurSong->m_Attacks.size() > 0 ) + { + pi->m_asModifiersQueue.push_back( GAMESTATE->m_pCurSong->m_Attacks ); + } + else + { + AttackArray aa; + pi->m_asModifiersQueue.push_back( aa ); + } } } diff --git a/stepmania/src/Song.h b/stepmania/src/Song.h index 036475d8ed..2e807fc4b3 100644 --- a/stepmania/src/Song.h +++ b/stepmania/src/Song.h @@ -3,6 +3,7 @@ #ifndef SONG_H #define SONG_H +#include "Attack.h" #include "TimingData.h" #include "Difficulty.h" #include "EnumHelper.h" @@ -144,6 +145,9 @@ public: RString m_sBackgroundFile; RString m_sCDTitleFile; + AttackArray m_Attacks; + vector m_sAttackString; + RString GetMusicPath() const; RString GetInstrumentTrackPath( InstrumentTrack it ) const; RString GetBannerPath() const; @@ -151,6 +155,7 @@ public: RString GetBackgroundPath() const; RString GetCDTitlePath() const; + /* For loading only: */ bool m_bHasMusic, m_bHasBanner;