[defaul -> Xcode4] We may need this sooner.

This commit is contained in:
Jason Felds
2011-05-21 12:33:57 -04:00
394 changed files with 97913 additions and 11546 deletions
+28 -3
View File
@@ -18,6 +18,8 @@
static Preference<bool> g_bShowMasks("ShowMasks", false);
PlayerNumber Actor::m_ActivePlayerNumber = PLAYER_1;
/**
* @brief Set up a hidden Actor that won't be drawn.
*
@@ -34,6 +36,8 @@ REGISTER_ACTOR_CLASS_WITH_NAME( HiddenActor, Actor );
float Actor::g_fCurrentBGMTime = 0, Actor::g_fCurrentBGMBeat;
float Actor::g_fCurrentBGMTimeNoOffset = 0, Actor::g_fCurrentBGMBeatNoOffset = 0;
vector<float> Actor::g_vfCurrentBGMBeatPlayer(NUM_PlayerNumber, 0);
vector<float> Actor::g_vfCurrentBGMBeatPlayerNoOffset(NUM_PlayerNumber, 0);
Actor *Actor::Copy() const { return new Actor(*this); }
@@ -68,6 +72,12 @@ void Actor::SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBe
g_fCurrentBGMBeatNoOffset = fBeatNoOffset;
}
void Actor::SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset )
{
g_vfCurrentBGMBeatPlayer[pn] = fBeat;
g_vfCurrentBGMBeatPlayerNoOffset[pn] = fBeatNoOffset;
}
void Actor::SetBGMLight( int iLightNumber, float fCabinetLights )
{
ASSERT( iLightNumber < NUM_CabinetLight );
@@ -150,8 +160,7 @@ Actor::Actor()
lua_setfield( L, -2, "ctx" );
lua_pop( L, 1 );
LUA->Release( L );
m_size = RageVector2( 1, 1 );
InitState();
m_pParent = NULL;
@@ -302,6 +311,11 @@ void Actor::BeginDraw() // set the world matrix and calculate actor properties
m_pTempState = &tempState;
tempState = m_current;
// XXX HACK! We can't really determine the active player outside Draw() so
// figure it out just for this clock type here.
if( m_EffectClock == CLOCK_BGM_BEAT_PLAYER_ACTIVE )
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[m_ActivePlayerNumber];
const float fTotalPeriod = GetEffectPeriod();
ASSERT( fTotalPeriod > 0 );
const float fTimeIntoEffect = fmodfp( m_fSecsIntoEffect+m_fEffectOffset, fTotalPeriod );
@@ -671,11 +685,22 @@ void Actor::UpdateInternal( float fDeltaTime )
break;
}
case CLOCK_BGM_BEAT_PLAYER_ACTIVE:
case CLOCK_BGM_BEAT:
m_fEffectDelta = g_fCurrentBGMBeat - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_fCurrentBGMBeat;
break;
case CLOCK_BGM_BEAT_PLAYER1:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_1] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_1];
break;
case CLOCK_BGM_BEAT_PLAYER2:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_2] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_2];
break;
case CLOCK_BGM_TIME:
m_fEffectDelta = g_fCurrentBGMTime - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_fCurrentBGMTime;
@@ -830,7 +855,7 @@ void Actor::SetEffectClockString( const RString &s )
{
if (s.EqualsNoCase("timer")) this->SetEffectClock( CLOCK_TIMER );
if (s.EqualsNoCase("timerglobal")) this->SetEffectClock( CLOCK_TIMER_GLOBAL );
else if(s.EqualsNoCase("beat")) this->SetEffectClock( CLOCK_BGM_BEAT );
else if(s.EqualsNoCase("beat")) this->SetEffectClock( CLOCK_BGM_BEAT_PLAYER_ACTIVE );
else if(s.EqualsNoCase("music")) this->SetEffectClock( CLOCK_BGM_TIME );
else if(s.EqualsNoCase("bgm")) this->SetEffectClock( CLOCK_BGM_BEAT ); // compat, deprecated
else if(s.EqualsNoCase("musicnooffset"))this->SetEffectClock( CLOCK_BGM_TIME_NO_OFFSET );
+14
View File
@@ -1,6 +1,7 @@
#ifndef ACTOR_H
#define ACTOR_H
#include "PlayerNumber.h"
#include "RageTypes.h"
#include "RageUtil_AutoPtr.h"
#include "LuaReference.h"
@@ -109,8 +110,16 @@ public:
virtual void LoadFromNode( const XNode* pNode );
static void SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBeatNoOffset );
static void SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset );
static void SetBGMLight( int iLightNumber, float fCabinetLights );
/**
* @brief The actively-drawing player number. This is used as a hack
* so that we don't need to tell each actor which player it belongs to.
* This is used to figure out the right player for "beat" effect clock.
*/
static PlayerNumber m_ActivePlayerNumber;
/**
* @brief The list of the different effects.
*
@@ -131,6 +140,9 @@ public:
CLOCK_BGM_BEAT,
CLOCK_BGM_TIME_NO_OFFSET,
CLOCK_BGM_BEAT_NO_OFFSET,
CLOCK_BGM_BEAT_PLAYER1,
CLOCK_BGM_BEAT_PLAYER2,
CLOCK_BGM_BEAT_PLAYER_ACTIVE,
CLOCK_LIGHT_1 = 1000,
CLOCK_LIGHT_LAST = 1100,
NUM_CLOCKS
@@ -713,6 +725,8 @@ protected:
// global state
static float g_fCurrentBGMTime, g_fCurrentBGMBeat;
static float g_fCurrentBGMTimeNoOffset, g_fCurrentBGMBeatNoOffset;
static vector<float> g_vfCurrentBGMBeatPlayer;
static vector<float> g_vfCurrentBGMBeatPlayerNoOffset;
private:
// commands
+3 -1
View File
@@ -116,7 +116,9 @@ Actor* ActorUtil::LoadFromNode( const XNode* pNode, Actor *pParentActor )
}
RString sClass;
pNode->GetAttrValue( "Class", sClass );
bool bHasClass = pNode->GetAttrValue( "Class", sClass );
if( !bHasClass )
bHasClass = pNode->GetAttrValue( "Type", sClass );
map<RString,CreateActorFn>::iterator iter = g_pmapRegistrees->find( sClass );
if( iter == g_pmapRegistrees->end() )
+12 -12
View File
@@ -58,7 +58,7 @@ void AdjustSync::ResetOriginalSyncData()
s_pTimingDataOriginal = new TimingData;
if( GAMESTATE->m_pCurSong )
*s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_Timing;
*s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_SongTiming;
else
*s_pTimingDataOriginal = TimingData();
s_fGlobalOffsetSecondsOriginal = PREFSMAN->m_fGlobalOffsetSeconds;
@@ -87,7 +87,7 @@ void AdjustSync::SaveSyncChanges()
{
if( GAMESTATE->IsCourseMode() )
return;
if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_Timing )
if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_SongTiming )
{
if( GAMESTATE->IsEditing() )
{
@@ -110,7 +110,7 @@ void AdjustSync::RevertSyncChanges()
if( GAMESTATE->IsCourseMode() )
return;
PREFSMAN->m_fGlobalOffsetSeconds.Set( s_fGlobalOffsetSecondsOriginal );
GAMESTATE->m_pCurSong->m_Timing = *s_pTimingDataOriginal;
GAMESTATE->m_pCurSong->m_SongTiming = *s_pTimingDataOriginal;
ResetOriginalSyncData();
s_fStandardDeviation = 0.0f;
s_fAverageError = 0.0f;
@@ -186,7 +186,7 @@ void AdjustSync::AutosyncOffset()
switch( GAMESTATE->m_SongOptions.GetCurrent().m_AutosyncType )
{
case SongOptions::AUTOSYNC_SONG:
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += mean;
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += mean;
break;
case SongOptions::AUTOSYNC_MACHINE:
PREFSMAN->m_fGlobalOffsetSeconds.Set( PREFSMAN->m_fGlobalOffsetSeconds + mean );
@@ -232,15 +232,15 @@ void AdjustSync::AutosyncTempo()
if( !CalcLeastSquares( s_vAutosyncTempoData, fSlope, fIntercept, fFilteredError ) )
return;
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += fIntercept;
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fIntercept;
const float fScaleBPM = 1.0f/(1.0f - fSlope);
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, i )
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments, i )
i->SetBPM( i->GetBPM() * fScaleBPM );
// We assume that the stops were measured as a number of beats.
// Therefore, if we change the bpms, we need to make a similar
// change to the stops.
FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, i )
FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments, i )
i->m_fStopSeconds *= 1.0f - fSlope;
SCREENMAN->SystemMessage( AUTOSYNC_CORRECTION_APPLIED.GetValue() );
@@ -296,7 +296,7 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_fBeat0OffsetInSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds, 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
@@ -309,10 +309,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments.size(); i++ )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments[i].GetBPM(), 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
@@ -330,10 +330,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments.size(); i++ )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_StopSegments[i].m_fStopSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_StopSegments[i].m_fStopSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments[i].m_fStopSeconds, 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
+14 -2
View File
@@ -37,7 +37,7 @@ void AnnouncerManager::GetAnnouncerNames( vector<RString>& AddTo )
// strip out the empty announcer folder
for( int i=AddTo.size()-1; i>=0; i-- )
if( !stricmp( AddTo[i], EMPTY_ANNOUNCER_NAME ) )
if( !AddTo[i].EqualsNoCase( EMPTY_ANNOUNCER_NAME ) )
AddTo.erase(AddTo.begin()+i, AddTo.begin()+i+1 );
}
@@ -49,7 +49,7 @@ bool AnnouncerManager::DoesAnnouncerExist( RString sAnnouncerName )
vector<RString> asAnnouncerNames;
GetAnnouncerNames( asAnnouncerNames );
for( unsigned i=0; i<asAnnouncerNames.size(); i++ )
if( 0==stricmp(sAnnouncerName, asAnnouncerNames[i]) )
if( sAnnouncerName.EqualsNoCase(asAnnouncerNames[i]) )
return true;
return false;
}
@@ -88,6 +88,18 @@ static const char *aliases[][2] = {
{ "ScreenSelectStyle comment solo", "select style comment solo" },
{ "ScreenSelectStyle comment versus", "select style comment versus" },
/* Combo compatibility: */
{ "gameplay combo 100", "gameplay 100 combo" },
{ "gameplay combo 200", "gameplay 200 combo" },
{ "gameplay combo 300", "gameplay 300 combo" },
{ "gameplay combo 400", "gameplay 400 combo" },
{ "gameplay combo 500", "gameplay 500 combo" },
{ "gameplay combo 600", "gameplay 600 combo" },
{ "gameplay combo 700", "gameplay 700 combo" },
{ "gameplay combo 800", "gameplay 800 combo" },
{ "gameplay combo 900", "gameplay 900 combo" },
{ "gameplay combo 1000", "gameplay 1000 combo" },
{ NULL, NULL }
};
+79 -22
View File
@@ -67,13 +67,13 @@ static float GetNoteFieldHeight( const PlayerState* pPlayerState )
namespace
{
float g_fExpandSeconds = 0;
struct PerPlayerData
{
float m_fMinTornadoX[MAX_COLS_PER_PLAYER];
float m_fMaxTornadoX[MAX_COLS_PER_PLAYER];
float m_fInvertDistance[MAX_COLS_PER_PLAYER];
float m_fBeatFactor;
float m_fExpandSeconds;
};
PerPlayerData g_EffectData[NUM_PLAYERS];
};
@@ -82,22 +82,25 @@ void ArrowEffects::Update()
{
const Style* pStyle = GAMESTATE->GetCurrentStyle();
{
static float fLastTime = 0;
float fTime = RageTimer::GetTimeSinceStartFast();
if( !GAMESTATE->m_bFreeze || !GAMESTATE->m_bDelay )
{
g_fExpandSeconds += fTime - fLastTime;
g_fExpandSeconds = fmodf( g_fExpandSeconds, PI*2 );
}
fLastTime = fTime;
}
FOREACH_PlayerNumber( pn )
{
const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pn];
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? GAMESTATE->m_pPlayerState[pn]->m_Position : GAMESTATE->m_Position;
PerPlayerData &data = g_EffectData[pn];
{
static float fLastTime = 0;
float fTime = RageTimer::GetTimeSinceStartFast();
if( !position.m_bFreeze || !position.m_bDelay )
{
data.m_fExpandSeconds += fTime - fLastTime;
data.m_fExpandSeconds = fmodf( data.m_fExpandSeconds, PI*2 );
}
fLastTime = fTime;
}
// Update Tornado
for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum )
{
@@ -176,7 +179,7 @@ void ArrowEffects::Update()
// Update Beat
do {
float fAccelTime = 0.2f, fTotalTime = 0.5f;
float fBeat = GAMESTATE->m_fSongBeatVisible + fAccelTime;
float fBeat = position.m_fSongBeatVisible + fAccelTime;
const bool bEvenBeat = ( int(fBeat) % 2 ) != 0;
@@ -208,6 +211,50 @@ void ArrowEffects::Update()
}
}
float GetSpeedMultiplier( float fSongBeat, float fMusicSeconds, const TimingData &tim )
{
if( tim.m_SpeedSegments.size() == 0 )
return 1.0;
const int index = tim.GetSpeedSegmentIndexAtBeat( fSongBeat );
const SpeedSegment &seg = tim.m_SpeedSegments[index];
float fStartBeat = NoteRowToBeat(seg.m_iStartRow);
float fStartTime = tim.GetElapsedTimeFromBeat( fStartBeat ) - tim.GetDelayAtBeat( fStartBeat );
float fEndTime;
float fCurTime = fMusicSeconds;
if( seg.m_usMode == 1 ) // seconds
{
fEndTime = fStartTime + seg.m_fWait;
}
else
{
fEndTime = tim.GetElapsedTimeFromBeat( fStartBeat + seg.m_fWait ) - tim.GetDelayAtBeat( fStartBeat + seg.m_fWait );
}
if( ( index == 0 && tim.m_SpeedSegments[0].m_fWait > 0.0 ) && fCurTime < fStartTime )
{
return 1.0;
}
else if( fEndTime >= fCurTime && ( index > 0 || tim.m_SpeedSegments[0].m_fWait > 0.0 ) )
{
const float fPriorSpeed = ( index == 0 ? 1 : tim.m_SpeedSegments[index - 1].m_fPercent );
float fTimeUsed = fCurTime - fStartTime;
float fDuration = fEndTime - fStartTime;
float fRatioUsed = fDuration == 0.0 ? 1 : fTimeUsed / fDuration;
float fDistance = fPriorSpeed - seg.m_fPercent;
float fRatioNeed = fRatioUsed * -fDistance;
return (fPriorSpeed + fRatioNeed);
}
else
{
return seg.m_fPercent;
}
}
/* 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 )
@@ -217,21 +264,27 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
bIsPastPeakOut = true;
float fYOffset = 0;
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? pPlayerState->m_Position : GAMESTATE->m_Position;
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( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 1.0f )
{
float fSongBeat = GAMESTATE->m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fYOffsetBeatSpacing = fBeatsUntilStep;
fYOffset += fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
float fSpeedMultiplier = ( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming ) ? 1.0 : GetSpeedMultiplier( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible, pCurSteps->m_Timing );
fYOffset += fSpeedMultiplier * fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
}
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 0.0f )
{
float fSongSeconds = GAMESTATE->m_fMusicSecondsVisible;
float fNoteSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fNoteBeat);
float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible;
float fNoteSeconds = pCurSteps->m_Timing.GetElapsedTimeFromBeat(fNoteBeat);
float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
float fBPM = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollBPM;
float fBPS = fBPM/60.f;
@@ -242,7 +295,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
// TODO: If we allow noteskins to have metricable row spacing
// (per issue 24), edit this to reflect that. -aj
fYOffset *= ARROW_SPACING;
// don't mess with the arrows after they've crossed 0
if( fYOffset < 0 )
return fYOffset * pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
@@ -288,6 +341,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
// Factor in scroll speed
float fScrollSpeed = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fRandomSpeed > 0 && !bAbsolute )
{
// Generate a deterministically "random" speed for each arrow.
@@ -307,7 +361,10 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
{
float fExpandMultiplier = SCALE( RageFastCos(g_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
// 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],
@@ -511,7 +568,7 @@ float ArrowEffects::GetRotationZ( const PlayerState* pPlayerState, float fNoteBe
// As usual, enable dizzy hold heads at your own risk. -Wolfman2000
if( fEffects[PlayerOptions::EFFECT_DIZZY] != 0 && ( DIZZY_HOLD_HEADS || !bIsHoldHead ) )
{
const float fSongBeat = GAMESTATE->m_fSongBeatVisible;
const float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fDizzyRotation = fNoteBeat - fSongBeat;
fDizzyRotation *= fEffects[PlayerOptions::EFFECT_DIZZY];
fDizzyRotation = fmodf( fDizzyRotation, 2*PI );
@@ -528,7 +585,7 @@ float ArrowEffects::ReceptorGetRotationZ( const PlayerState* pPlayerState )
if( fEffects[PlayerOptions::EFFECT_CONFUSION] != 0 )
{
float fConfRotation = GAMESTATE->m_fSongBeatVisible;
float fConfRotation = pPlayerState->m_Position.m_fSongBeatVisible;
fConfRotation *= fEffects[PlayerOptions::EFFECT_CONFUSION];
fConfRotation = fmodf( fConfRotation, 2*PI );
fConfRotation *= -180/PI;
@@ -680,7 +737,7 @@ float ArrowEffects::GetBrightness( const PlayerState* pPlayerState, float fNoteB
if( GAMESTATE->IsEditing() )
return 1;
float fSongBeat = GAMESTATE->m_fSongBeatVisible;
float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fBrightness = SCALE( fBeatsUntilStep, 0, -1, 1.f, 0.f );
+8 -6
View File
@@ -11,9 +11,10 @@ void Attack::GetAttackBeats( const Song *pSong, float &fStartBeat, float &fEndBe
{
ASSERT( pSong );
ASSERT_M( fStartSecond >= 0, ssprintf("StartSecond: %f",fStartSecond) );
fStartBeat = pSong->GetBeatFromElapsedTime( fStartSecond );
fEndBeat = pSong->GetBeatFromElapsedTime( fStartSecond+fSecsRemaining );
const TimingData &timing = pSong->m_SongTiming;
fStartBeat = timing.GetBeatFromElapsedTime( fStartSecond );
fEndBeat = timing.GetBeatFromElapsedTime( fStartSecond+fSecsRemaining );
}
/* Get the range for an attack that's being applied in realtime, eg. during battle
@@ -31,12 +32,13 @@ void Attack::GetRealtimeAttackBeats( const Song *pSong, const PlayerState* pPlay
ASSERT( pSong );
/* If reasonable, push the attack forward 8 beats so that notes on screen don't change suddenly. */
fStartBeat = min( GAMESTATE->m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = min( GAMESTATE->m_Position.m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = truncf(fStartBeat)+1;
const float lStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat );
const TimingData &timing = pSong->m_SongTiming;
const float lStartSecond = timing.GetElapsedTimeFromBeat( fStartBeat );
const float fEndSecond = lStartSecond + fSecsRemaining;
fEndBeat = pSong->GetBeatFromElapsedTime( fEndSecond );
fEndBeat = timing.GetBeatFromElapsedTime( fEndSecond );
fEndBeat = truncf(fEndBeat)+1;
// loading the course should have caught this.
+1 -1
View File
@@ -99,7 +99,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain )
if( tn[pn].iKeysoundIndex >= 0 )
{
RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex];
float fSeconds = pSong->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency();
float fSeconds = GAMESTATE->m_pCurSteps[pn]->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency();
float fPan = 0;
if( !bSoundIsGlobal )
+22 -12
View File
@@ -6,6 +6,7 @@
#include "ActorUtil.h"
#include "Foreach.h"
#include "LuaManager.h"
#include "PrefsManager.h"
REGISTER_ACTOR_CLASS(BGAnimation);
@@ -102,18 +103,27 @@ void BGAnimation::LoadFromAniDir( const RString &_sAniDir )
if( DoesFileExist(sPathToIni) )
{
// This is a 3.9-style BGAnimation (using .ini)
IniFile ini;
ini.ReadFile( sPathToIni );
AddLayersFromAniDir( sAniDir, &ini ); // TODO: Check for circular load
XNode* pBGAnimation = ini.GetChild( "BGAnimation" );
XNode dummy( "BGAnimation" );
if( pBGAnimation == NULL )
pBGAnimation = &dummy;
LoadFromNode( pBGAnimation );
if( PREFSMAN->m_bQuirksMode )
{
// This is a 3.9-style BGAnimation (using .ini)
IniFile ini;
ini.ReadFile( sPathToIni );
AddLayersFromAniDir( sAniDir, &ini ); // TODO: Check for circular load
XNode* pBGAnimation = ini.GetChild( "BGAnimation" );
XNode dummy( "BGAnimation" );
if( pBGAnimation == NULL )
pBGAnimation = &dummy;
LoadFromNode( pBGAnimation );
}
else // We don't officially support .ini files anymore.
{
XNode dummy( "BGAnimation" );
XNode *pBG = &dummy;
LoadFromNode( pBG );
}
}
else
{
+7 -7
View File
@@ -388,30 +388,30 @@ void BGAnimationLayer::LoadFromNode( const XNode* pNode )
pNode->GetAttrValue( "Stretch", bStretch );
// Check for string match first, then do integer match.
// "if(atoi(type)==0)" was matching against all string matches.
// "if(StringType(type)==0)" was matching against all string matches.
// -Chris
if( stricmp(type,"sprite")==0 )
if( type.EqualsNoCase("sprite") )
{
m_Type = TYPE_SPRITE;
}
else if( stricmp(type,"particles")==0 )
else if( type.EqualsNoCase("particles") )
{
m_Type = TYPE_PARTICLES;
}
else if( stricmp(type,"tiles")==0 )
else if( type.EqualsNoCase("tiles") )
{
m_Type = TYPE_TILES;
}
else if( atoi(type) == 1 )
else if( StringToInt(type) == 1 )
{
m_Type = TYPE_SPRITE;
bStretch = true;
}
else if( atoi(type) == 2 )
else if( StringToInt(type) == 2 )
{
m_Type = TYPE_PARTICLES;
}
else if( atoi(type) == 3 )
else if( StringToInt(type) == 3 )
{
m_Type = TYPE_TILES;
}
+17 -3
View File
@@ -32,6 +32,7 @@ void BPMDisplay::Load()
SET_EXTRA_COMMAND.Load( m_sName, "SetExtraCommand" );
CYCLE.Load( m_sName, "Cycle" );
RANDOM_CYCLE_SPEED.Load( m_sName, "RandomCycleSpeed" );
COURSE_CYCLE_SPEED.Load( m_sName, "CourseCycleSpeed" );
SEPARATOR.Load( m_sName, "Separator" );
SHOW_QMARKS.Load( m_sName, "ShowQMarksInRandomCycle" );
NO_BPM_TEXT.Load( m_sName, "NoBpmText" );
@@ -204,9 +205,11 @@ void BPMDisplay::SetBpmFromCourse( const Course* pCourse )
StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
Trail *pTrail = pCourse->GetTrail( st );
ASSERT( pTrail );
// GetTranslitFullTitle because "Crashinfo.txt is garbled because of the ANSI output as usual." -f
ASSERT_M( pTrail, ssprintf("Course '%s' has no trail for StepsType '%s'", pCourse->GetTranslitFullTitle().c_str(), StringConversion::ToString(st).c_str() ) );
m_fCycleTime = 0.2f;
// todo: let themers define this. -aj
m_fCycleTime = (float)COURSE_CYCLE_SPEED;
if( (int)pTrail->m_vEntries.size() > CommonMetrics::MAX_COURSE_ENTRIES_BEFORE_VARIOUS )
{
@@ -276,7 +279,7 @@ SongBPMDisplay::SongBPMDisplay()
void SongBPMDisplay::Update( float fDeltaTime )
{
float fGameStateBPM = GAMESTATE->m_fCurBPS * 60.0f;
float fGameStateBPM = GAMESTATE->m_Position.m_fCurBPS * 60.0f;
if( m_fLastGameStateBPM != fGameStateBPM )
{
m_fLastGameStateBPM = fGameStateBPM;
@@ -304,12 +307,23 @@ public:
}
return 0;
}
static int SetFromCourse( T* p, lua_State *L )
{
if( lua_isnil(L,1) ) { p->NoBPM(); }
else
{
const Course* pCourse = Luna<Course>::check( L, 1, true );
p->SetBpmFromCourse(pCourse);
}
return 0;
}
static int GetText( T* p, lua_State *L ) { lua_pushstring( L, p->GetText() ); return 1; }
LunaBPMDisplay()
{
ADD_METHOD( SetFromGameState );
ADD_METHOD( SetFromSong );
ADD_METHOD( SetFromCourse );
ADD_METHOD( GetText );
}
};
+1
View File
@@ -82,6 +82,7 @@ protected:
ThemeMetric<bool> SHOW_QMARKS;
/** @brief How often the random BPMs cycle themselves. */
ThemeMetric<float> RANDOM_CYCLE_SPEED;
ThemeMetric<float> COURSE_CYCLE_SPEED;
/** @brief The text used to separate the low and high BPMs. */
ThemeMetric<RString> SEPARATOR;
/** @brief The text used when there is no BPM. */
+5 -5
View File
@@ -419,7 +419,7 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
int iStartRow = BeatToNoteRow(fFirstBeat);
int iEndRow = BeatToNoteRow(fEndBeat);
const TimingData &timing = m_pSong->m_Timing;
const TimingData &timing = m_pSong->m_SongTiming;
// change BG every time signature change or 4 measures
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
@@ -697,7 +697,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
float fBeat, fBPS, fThrowAway;
bool bFreeze;
int iThrowAway;
pSong->m_Timing.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
// Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it.
const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -762,7 +762,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
m_pCurrentBGA->PlayCommand( "GainFocus" );
/* How much time of this BGA have we skipped? (This happens with SetSeconds.) */
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( change.m_fStartBeat );
const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat );
/* This is affected by the music rate. */
fDeltaTime = fCurrentTime - fStartSecond;
@@ -800,9 +800,9 @@ void BackgroundImpl::Update( float fDeltaTime )
FOREACH_BackgroundLayer( i )
{
Layer &layer = m_Layer[i];
layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_fMusicSeconds, m_mapNameToTransition );
layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition );
}
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}
void BackgroundImpl::DrawPrimitives()
+4 -2
View File
@@ -133,7 +133,8 @@ void BackgroundUtil::GetBackgroundTransitions( const RString &_sName, vector<RSt
sName = "*";
vsPathsOut.clear();
GetDirListing( BACKGROUND_TRANSITIONS_DIR+sName+".xml", vsPathsOut, false, true );
if( true )
GetDirListing( BACKGROUND_TRANSITIONS_DIR+sName+".xml", vsPathsOut, false, true );
GetDirListing( BACKGROUND_TRANSITIONS_DIR+sName+".lua", vsPathsOut, false, true );
vsNamesOut.clear();
@@ -241,7 +242,8 @@ void BackgroundUtil::GetGlobalBGAnimations( const Song *pSong, const RString &sM
{
vsPathsOut.clear();
GetDirListing( BG_ANIMS_DIR+sMatch+"*", vsPathsOut, true, true );
GetDirListing( BG_ANIMS_DIR+sMatch+"*.xml", vsPathsOut, false, true );
if( true )
GetDirListing( BG_ANIMS_DIR+sMatch+"*.xml", vsPathsOut, false, true );
vsNamesOut.clear();
FOREACH_CONST( RString, vsPathsOut, s )
+4 -4
View File
@@ -17,8 +17,8 @@ REGISTER_ACTOR_CLASS( Banner );
ThemeMetric<bool> SCROLL_RANDOM ("Banner","ScrollRandom");
ThemeMetric<bool> SCROLL_ROULETTE ("Banner","ScrollRoulette");
//ThemeMetric<bool> SCROLL_MODE ("Banner","ScrollMode");
//ThemeMetric<bool> SCROLL_SORT_ORDER ("Banner","ScrollSortOrder");
ThemeMetric<bool> SCROLL_MODE ("Banner","ScrollMode");
ThemeMetric<bool> SCROLL_SORT_ORDER ("Banner","ScrollSortOrder");
ThemeMetric<float> SCROLL_SPEED_DIVISOR ("Banner","ScrollSpeedDivisor");
Banner::Banner()
@@ -119,7 +119,7 @@ void Banner::LoadFromSong( Song* pSong ) // NULL means no song
void Banner::LoadMode()
{
Load( THEME->GetPathG("Banner","Mode") );
m_bScrolling = false;
m_bScrolling = (bool)SCROLL_MODE;
}
void Banner::LoadFromSongGroup( RString sSongGroup )
@@ -229,7 +229,7 @@ void Banner::LoadFromSortOrder( SortOrder so )
if( so != SORT_GROUP && so != SORT_RECENT )
Load( THEME->GetPathG("Banner",ssprintf("%s",SortOrderToString(so).c_str())) );
}
m_bScrolling = false;
m_bScrolling = (bool)SCROLL_SORT_ORDER;
}
// lua start
-55
View File
@@ -293,23 +293,14 @@ RageTextureID BannerCache::LoadCachedBanner( RString sBannerPath )
ASSERT( pImage );
int iSourceWidth = 0, iSourceHeight = 0;
bool bWasRotatedBanner = false;
BannerData.GetValue( sBannerPath, "Width", iSourceWidth );
BannerData.GetValue( sBannerPath, "Height", iSourceHeight );
BannerData.GetValue( sBannerPath, "Rotated", bWasRotatedBanner );
if( iSourceWidth == 0 || iSourceHeight == 0 )
{
LOG->UserLog( "Cache file", sBannerPath, "couldn't be loaded." );
return ID;
}
if( bWasRotatedBanner )
{
/* We need to tell Sprite that this was originally a rotated
* sprite. */
ID.filename += "(was rotated)";
}
/* Is the banner already in a texture? */
if( TEXTUREMAN->IsTextureRegistered(ID) )
return ID; /* It's all set. */
@@ -383,50 +374,6 @@ void BannerCache::CacheBannerInternal( RString sBannerPath )
return;
}
bool bWasRotatedBanner = false;
if( Sprite::IsDiagonalBanner(pImage->w , pImage->h) )
{
/* Ack. It's a diagonal banner. Problem: if we resize a diagonal banner, we
* get ugly checker patterns. We need to un-rotate it.
*
* If we spin the banner by hand, we need to do a linear filter, or the
* fade to the full resolution banner is misaligned, which looks strange.
*
* To do a linear filter, we need to lose the palette. Oh well.
*
* This also makes the banner take less memory, though that could also be
* done by RLEing the surface.
*/
//RageSurfaceUtils::ApplyHotPinkColorKey( pImage );
RageSurfaceUtils::ConvertSurface(pImage, pImage->w, pImage->h, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
RageSurface *dst = CreateSurface(
256, 64, pImage->format->BitsPerPixel,
pImage->format->Rmask, pImage->format->Gmask, pImage->format->Bmask, pImage->format->Amask );
if( pImage->format->BitsPerPixel == 8 )
{
ASSERT( pImage->format->palette );
dst->fmt.palette = pImage->fmt.palette;
}
const float fCustomImageCoords[8] = {
0.02f, 0.78f, // top left
0.22f, 0.98f, // bottom left
0.98f, 0.22f, // bottom right
0.78f, 0.02f, // top right
};
RageSurfaceUtils::BlitTransform( pImage, dst, fCustomImageCoords );
delete pImage;
pImage = dst;
bWasRotatedBanner = true;
}
const int iSourceWidth = pImage->w, iSourceHeight = pImage->h;
int iWidth = pImage->w / 2, iHeight = pImage->h / 2;
@@ -504,8 +451,6 @@ void BannerCache::CacheBannerInternal( RString sBannerPath )
BannerData.SetValue( sBannerPath, "Width", iSourceWidth );
BannerData.SetValue( sBannerPath, "Height", iSourceHeight );
BannerData.SetValue( sBannerPath, "FullHash", GetHashForFile( sBannerPath ) );
/* Remember this, so we can hint Sprite. */
BannerData.SetValue( sBannerPath, "Rotated", bWasRotatedBanner );
BannerData.WriteFile( BANNER_CACHE_INDEX );
}
+8 -8
View File
@@ -191,7 +191,7 @@ void BeginnerHelper::ShowStepCircle( PlayerNumber pn, int CSTEP )
m_sStepCircle[pn][isc].StopEffect();
m_sStepCircle[pn][isc].SetZoom( 2 );
m_sStepCircle[pn][isc].StopTweening();
m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_fCurBPS/3, TWEEN_LINEAR );
m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_Position.m_fCurBPS/3, TWEEN_LINEAR );
m_sStepCircle[pn][isc].SetZoom( 0 );
}
@@ -318,19 +318,19 @@ void BeginnerHelper::Step( PlayerNumber pn, int CSTEP )
ShowStepCircle( pn, ST_DOWN );
m_pDancer[pn]->StopTweening();
m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f );
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS/8, TWEEN_LINEAR );
m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS/8, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 90 );
m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_fCurBPS * 2) ); //sleep between jump-frames
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS /6, TWEEN_LINEAR );
m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_Position.m_fCurBPS * 2) ); //sleep between jump-frames
m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 0 );
break;
}
m_sFlash.StopEffect();
m_sFlash.StopTweening();
m_sFlash.Sleep( GAMESTATE->m_fCurBPS/16 );
m_sFlash.Sleep( GAMESTATE->m_Position.m_fCurBPS/16 );
m_sFlash.SetDiffuseAlpha( 1 );
m_sFlash.BeginTweening( 1/GAMESTATE->m_fCurBPS * 0.5f );
m_sFlash.BeginTweening( 1/GAMESTATE->m_Position.m_fCurBPS * 0.5f );
m_sFlash.SetDiffuseAlpha( 0 );
}
@@ -340,7 +340,7 @@ void BeginnerHelper::Update( float fDeltaTime )
return;
// the row we want to check on this update
int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat + 0.4f );
int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.4f );
FOREACH_EnabledPlayer( pn )
{
for( int iRow=m_iLastRowChecked; iRow<iCurRow; iRow++ )
@@ -369,7 +369,7 @@ void BeginnerHelper::Update( float fDeltaTime )
m_pDancePad->Update( fDeltaTime );
m_sFlash.Update( fDeltaTime );
float beat = fDeltaTime*GAMESTATE->m_fCurBPS;
float beat = fDeltaTime*GAMESTATE->m_Position.m_fCurBPS;
// If this is not a human player, the dancer is not shown
FOREACH_HumanPlayer( pu )
{
+2 -2
View File
@@ -90,8 +90,8 @@ int CourseEntry::GetNumModChanges() const
Course::Course(): m_bIsAutogen(false), m_sPath(""), m_sMainTitle(""),
m_sMainTitleTranslit(""), m_sSubTitle(""), m_sSubTitleTranslit(""),
m_sBannerPath(""), m_sBackgroundPath(""), m_sCDTitlePath(""),
m_sGroupName(""), m_sScripter(""), m_bRepeat(false), m_fGoalSeconds(0),
m_sScripter(""), m_sBannerPath(""), m_sBackgroundPath(""),
m_sCDTitlePath(""), m_sGroupName(""), m_bRepeat(false), m_fGoalSeconds(0),
m_bShuffle(false), m_iLives(-1), m_bSortByMeter(false),
m_bIncomplete(false), m_vEntries(), m_SortOrder_TotalDifficulty(0),
m_SortOrder_Ranking(0), m_LoadedFromProfile(ProfileSlot_Invalid),
+25 -25
View File
@@ -61,13 +61,13 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
const MsdFile::value_t &sParams = msd.GetValue(i);
// handle the data
if( 0 == stricmp(sValueName, "COURSE") )
if( sValueName.EqualsNoCase("COURSE") )
out.m_sMainTitle = sParams[1];
else if( 0 == stricmp(sValueName, "COURSETRANSLIT") )
else if( sValueName.EqualsNoCase("COURSETRANSLIT") )
out.m_sMainTitleTranslit = sParams[1];
else if( 0 == stricmp(sValueName, "SCRIPTER") )
else if( sValueName.EqualsNoCase("SCRIPTER") )
out.m_sScripter = sParams[1];
else if( 0 == stricmp(sValueName, "REPEAT") )
else if( sValueName.EqualsNoCase("REPEAT") )
{
RString str = sParams[1];
str.MakeLower();
@@ -75,27 +75,27 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
out.m_bRepeat = true;
}
else if( 0 == stricmp(sValueName, "BANNER") )
else if( sValueName.EqualsNoCase("BANNER") )
{
out.m_sBannerPath = sParams[1];
}
else if( 0 == stricmp(sValueName, "BACKGROUND") )
else if( sValueName.EqualsNoCase("BACKGROUND") )
{
out.m_sBackgroundPath = sParams[1];
}
else if( 0 == stricmp(sValueName, "LIVES") )
else if( sValueName.EqualsNoCase("LIVES") )
{
out.m_iLives = max( atoi(sParams[1]), 0 );
out.m_iLives = max( StringToInt(sParams[1]), 0 );
}
else if( 0 == stricmp(sValueName, "GAINSECONDS") )
else if( sValueName.EqualsNoCase("GAINSECONDS") )
{
fGainSeconds = StringToFloat( sParams[1] );
}
else if( 0 == stricmp(sValueName, "METER") )
else if( sValueName.EqualsNoCase("METER") )
{
if( sParams.params.size() == 2 )
{
out.m_iCustomMeter[Difficulty_Medium] = max( atoi(sParams[1]), 0 ); /* compat */
out.m_iCustomMeter[Difficulty_Medium] = max( StringToInt(sParams[1]), 0 ); /* compat */
}
else if( sParams.params.size() == 3 )
{
@@ -105,12 +105,12 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
LOG->UserLog( "Course file", sPath, "contains an invalid #METER string: \"%s\"", sParams[1].c_str() );
continue;
}
out.m_iCustomMeter[cd] = max( atoi(sParams[2]), 0 );
out.m_iCustomMeter[cd] = max( StringToInt(sParams[2]), 0 );
}
}
// todo: add COMBO and COMBOMODE from DWI CRS files? -aj
else if( 0 == stricmp(sValueName, "MODS") )
else if( sValueName.EqualsNoCase("MODS") )
{
Attack attack;
float end = -9999;
@@ -156,7 +156,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
}
}
else if( 0 == stricmp(sValueName, "SONG") )
else if( sValueName.EqualsNoCase("SONG") )
{
CourseEntry new_entry;
@@ -167,28 +167,28 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
// most played
if( sParams[1].Left(strlen("BEST")) == "BEST" )
{
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_MostPlays;
}
// least played
else if( sParams[1].Left(strlen("WORST")) == "WORST" )
{
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_FewestPlays;
}
// best grades
else if( sParams[1].Left(strlen("GRADEBEST")) == "GRADEBEST" )
{
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("GRADEBEST")) ) - 1;
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("GRADEBEST")) ) - 1;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_TopGrades;
}
// worst grades
else if( sParams[1].Left(strlen("GRADEWORST")) == "GRADEWORST" )
{
new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("GRADEWORST")) ) - 1;
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("GRADEWORST")) ) - 1;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_LowestGrades;
}
@@ -284,7 +284,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
else if( !sMod.CompareNoCase("nodifficult") )
new_entry.bNoDifficult = true;
else if( sMod.length() > 5 && !sMod.Left(5).CompareNoCase("award") )
new_entry.iGainLives = atoi( sMod.substr(5).c_str() );
new_entry.iGainLives = StringToInt( sMod.substr(5) );
else
continue;
mods.erase( mods.begin() + j );
@@ -298,22 +298,22 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
out.m_vEntries.push_back( new_entry );
}
else if( !stricmp(sValueName, "DISPLAYCOURSE") || !stricmp(sValueName, "COMBO") ||
!stricmp(sValueName, "COMBOMODE") )
else if( !sValueName.EqualsNoCase("DISPLAYCOURSE") || !sValueName.EqualsNoCase("COMBO") ||
!sValueName.EqualsNoCase("COMBOMODE") )
{
// Ignore
}
else if( bFromCache && !stricmp(sValueName, "RADAR") )
else if( bFromCache && !sValueName.EqualsNoCase("RADAR") )
{
StepsType st = (StepsType) atoi(sParams[1]);
CourseDifficulty cd = (CourseDifficulty) atoi( sParams[2] );
StepsType st = (StepsType) StringToInt(sParams[1]);
CourseDifficulty cd = (CourseDifficulty) StringToInt( sParams[2] );
RadarValues rv;
rv.FromString( sParams[3] );
out.m_RadarCache[Course::CacheEntry(st, cd)] = rv;
}
else if( 0 == stricmp(sValueName, "STYLE") )
else if( sValueName.EqualsNoCase("STYLE") )
{
RString sStyles = sParams[1];
vector<RString> asStyles;
+25
View File
@@ -404,6 +404,23 @@ RString CryptManager::GetSHA1ForString( RString sData )
return RString( (const char *) digest, sizeof(digest) );
}
RString CryptManager::GetSHA1ForFile( RString fn )
{
RageFile file;
if( !file.Open( fn, RageFile::READ ) )
{
LOG->Warn( "GetSHA1: Failed to open file '%s'", fn.c_str() );
return RString();
}
int iHash = register_hash( &sha1_desc );
ASSERT( iHash >= 0 );
unsigned char digest[20];
HashFile( file, digest, iHash );
return RString( (const char *) digest, sizeof(digest) );
}
RString CryptManager::GetPublicKeyFileName()
{
return PUBLIC_KEY_PATH;
@@ -455,12 +472,20 @@ public:
lua_pushstring( L, sha1out );
return 1;
}
static int SHA1File( T* p, lua_State *L )
{
RString sha1fout;
sha1fout = p->GetSHA1ForFile(SArg(1));
lua_pushstring( L, sha1fout );
return 1;
}
LunaCryptManager()
{
ADD_METHOD( MD5String );
ADD_METHOD( MD5File );
ADD_METHOD( SHA1String );
ADD_METHOD( SHA1File );
}
};
+1
View File
@@ -27,6 +27,7 @@ public:
static RString GetMD5ForFile( RString fn ); // in binary
static RString GetMD5ForString( RString sData ); // in binary
static RString GetSHA1ForString( RString sData ); // in binary
static RString GetSHA1ForFile( RString fn ); // in binary
static RString GetPublicKeyFileName();
+7 -7
View File
@@ -175,7 +175,7 @@ int Neg1OrPos1() { return RandomInt( 2 ) ? -1 : +1; }
void DancingCharacters::Update( float fDelta )
{
if( GAMESTATE->m_bFreeze || GAMESTATE->m_bDelay )
if( GAMESTATE->m_Position.m_bFreeze || GAMESTATE->m_Position.m_bDelay )
{
// spin the camera Matrix-style
m_CameraPanYStart += fDelta*40;
@@ -184,7 +184,7 @@ void DancingCharacters::Update( float fDelta )
else
{
// make the characters move
float fBPM = GAMESTATE->m_fCurBPS*60;
float fBPM = GAMESTATE->m_Position.m_fCurBPS*60;
float fUpdateScale = SCALE( fBPM, 60.f, 300.f, 0.75f, 1.5f );
CLAMP( fUpdateScale, 0.75f, 1.5f );
@@ -209,8 +209,8 @@ void DancingCharacters::Update( float fDelta )
}
bWasGameplayStarting = bGameplayStarting;
static float fLastBeat = GAMESTATE->m_fSongBeat;
float fThisBeat = GAMESTATE->m_fSongBeat;
static float fLastBeat = GAMESTATE->m_Position.m_fSongBeat;
float fThisBeat = GAMESTATE->m_Position.m_fSongBeat;
if( fLastBeat < GAMESTATE->m_pCurSong->m_fFirstBeat &&
fThisBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat )
{
@@ -220,7 +220,7 @@ void DancingCharacters::Update( float fDelta )
fLastBeat = fThisBeat;
// time for a new sweep?
if( GAMESTATE->m_fSongBeat > m_fThisCameraEndBeat )
if( GAMESTATE->m_Position.m_fSongBeat > m_fThisCameraEndBeat )
{
if( RandomInt(6) >= 4 )
{
@@ -248,7 +248,7 @@ void DancingCharacters::Update( float fDelta )
m_fLookAtHeight = CAMERA_STILL_LOOK_AT_HEIGHT;
}
int iCurBeat = (int)GAMESTATE->m_fSongBeat;
int iCurBeat = (int)GAMESTATE->m_Position.m_fSongBeat;
iCurBeat -= iCurBeat%8;
m_fThisCameraStartBeat = (float) iCurBeat;
@@ -313,7 +313,7 @@ void DancingCharacters::DrawPrimitives()
if(m_fThisCameraStartBeat == m_fThisCameraEndBeat)
fPercentIntoSweep = 0;
else
fPercentIntoSweep = SCALE(GAMESTATE->m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f );
fPercentIntoSweep = SCALE(GAMESTATE->m_Position.m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f );
float fCameraPanY = SCALE( fPercentIntoSweep, 0.f, 1.f, m_CameraPanYStart, m_CameraPanYEnd );
float fCameraHeight = SCALE( fPercentIntoSweep, 0.f, 1.f, m_fCameraHeightStart, m_fCameraHeightEnd );
-29
View File
@@ -21,35 +21,6 @@ XToString( Difficulty );
StringToX( Difficulty );
LuaXType( Difficulty );
/* We prefer the above names; recognize a number of others, too. (They'll get
* normalized when written to SMs, etc.) TODO: Format specific hacks should be
* moved into the file loader for that format. We don't want to carry these
* hacks forward to file formats that don't need them. */
Difficulty DwiCompatibleStringToDifficulty( const RString& sDC )
{
RString s2 = sDC;
s2.MakeLower();
if( s2 == "beginner" ) return Difficulty_Beginner;
else if( s2 == "easy" ) return Difficulty_Easy;
else if( s2 == "basic" ) return Difficulty_Easy;
else if( s2 == "light" ) return Difficulty_Easy;
else if( s2 == "medium" ) return Difficulty_Medium;
else if( s2 == "another" ) return Difficulty_Medium;
else if( s2 == "trick" ) return Difficulty_Medium;
else if( s2 == "standard" ) return Difficulty_Medium;
else if( s2 == "difficult") return Difficulty_Medium;
else if( s2 == "hard" ) return Difficulty_Hard;
else if( s2 == "ssr" ) return Difficulty_Hard;
else if( s2 == "maniac" ) return Difficulty_Hard;
else if( s2 == "heavy" ) return Difficulty_Hard;
else if( s2 == "smaniac" ) return Difficulty_Challenge;
else if( s2 == "challenge" ) return Difficulty_Challenge;
else if( s2 == "expert" ) return Difficulty_Challenge;
else if( s2 == "oni" ) return Difficulty_Challenge;
else if( s2 == "edit" ) return Difficulty_Edit;
else return Difficulty_Invalid;
}
const RString &CourseDifficultyToLocalizedString( CourseDifficulty x )
{
static auto_ptr<LocalizedString> g_CourseDifficultyName[NUM_Difficulty];
-2
View File
@@ -22,8 +22,6 @@ const RString& DifficultyToString( Difficulty dc );
Difficulty StringToDifficulty( const RString& sDC );
LuaDeclareType( Difficulty );
Difficulty DwiCompatibleStringToDifficulty( const RString& sDC );
typedef Difficulty CourseDifficulty;
const int NUM_CourseDifficulty = NUM_Difficulty;
/** @brief Loop through the shown course difficulties. */
+1 -1
View File
@@ -386,7 +386,7 @@ void EditMenu::OnRowValueChanged( EditMenuRow row )
dcOld = GetSelectedDifficulty();
m_vpSteps.clear();
FOREACH_ENUM( Difficulty, dc )
{
if( dc == Difficulty_Edit )
+3 -3
View File
@@ -272,14 +272,14 @@ void FileTransfer::HTTPUpdate()
m_sResponseName = "Malformed response.";
return;
}
m_iResponseCode = atoi(m_sBUFFER.substr(i+1,j-i).c_str());
m_iResponseCode = StringToInt(m_sBUFFER.substr(i+1,j-i));
m_sResponseName = m_sBUFFER.substr( j+1, k-j );
i = m_sBUFFER.find("Content-Length:");
j = m_sBUFFER.find("\n", i+1 );
if( i != string::npos )
m_iTotalBytes = atoi(m_sBUFFER.substr(i+16,j-i).c_str());
m_iTotalBytes = StringToInt(m_sBUFFER.substr(i+16,j-i));
else
m_iTotalBytes = -1; // We don't know, so go until disconnect
@@ -350,7 +350,7 @@ bool FileTransfer::ParseHTTPAddress( const RString &URL, RString &sProto, RStrin
sServer = asMatches[1];
if( asMatches[3] != "" )
{
iPort = atoi(asMatches[3]);
iPort = StringToInt(asMatches[3]);
if( iPort == 0 )
return false;
}
+2 -2
View File
@@ -414,7 +414,7 @@ void Font::LoadFontPageSettings( FontPageSettings &cfg, IniFile &ini, const RStr
// If val is an integer, it's a width, eg. "10=27".
if( IsAnInt(sName) )
{
cfg.m_mapGlyphWidths[atoi(sName)] = pValue->GetValue<int>();
cfg.m_mapGlyphWidths[StringToInt(sName)] = pValue->GetValue<int>();
continue;
}
@@ -516,7 +516,7 @@ void Font::LoadFontPageSettings( FontPageSettings &cfg, IniFile &ini, const RStr
TrimLeft( sRowStr );
ASSERT( IsAnInt(sRowStr) );
const int iRow = atoi( sRowStr.c_str() );
const int iRow = StringToInt( sRowStr );
const int iFirstFrame = iRow * iNumFramesWide;
if( iRow > iNumFramesHigh )
+8 -8
View File
@@ -41,9 +41,9 @@ void Foreground::LoadFromSong( const Song *pSong )
bga.m_fStartBeat = change.m_fStartBeat;
bga.m_bFinished = false;
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat );
const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
const float fStopSecond = fStartSecond + bga.m_bga->GetTweenTimeLeft();
bga.m_fStopBeat = pSong->m_Timing.GetBeatFromElapsedTime( fStopSecond );
bga.m_fStopBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( fStopSecond );
bga.m_bga->SetVisible( false );
@@ -65,7 +65,7 @@ void Foreground::Update( float fDeltaTime )
{
LoadedBGA &bga = m_BGAnimations[i];
if( GAMESTATE->m_fSongBeat < bga.m_fStartBeat )
if( GAMESTATE->m_Position.m_fSongBeat < bga.m_fStartBeat )
{
// The animation hasn't started yet.
continue;
@@ -82,12 +82,12 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->SetVisible( true );
bga.m_bga->PlayCommand( "On" );
const float fStartSecond = m_pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat );
lDeltaTime = GAMESTATE->m_fMusicSeconds - fStartSecond;
const float fStartSecond = m_pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - fStartSecond;
}
else
{
lDeltaTime = GAMESTATE->m_fMusicSeconds - m_fLastMusicSeconds;
lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - m_fLastMusicSeconds;
}
// This shouldn't go down, but be safe:
@@ -95,7 +95,7 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->Update( lDeltaTime / fRate );
if( GAMESTATE->m_fSongBeat > bga.m_fStopBeat )
if( GAMESTATE->m_Position.m_fSongBeat > bga.m_fStopBeat )
{
// Finished.
bga.m_bga->SetVisible( false );
@@ -104,7 +104,7 @@ void Foreground::Update( float fDeltaTime )
}
}
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}
/*
+2 -2
View File
@@ -335,12 +335,12 @@ void GameCommand::LoadOne( const Command& cmd )
else if( sName == "weight" )
{
m_iWeightPounds = atoi( sValue );
m_iWeightPounds = StringToInt( sValue );
}
else if( sName == "goalcalories" )
{
m_iGoalCalories = atoi( sValue );
m_iGoalCalories = StringToInt( sValue );
}
else if( sName == "goaltype" )
+2
View File
@@ -87,8 +87,10 @@ enum StepsType
StepsType_para_single,
StepsType_ds3ddx_single,
StepsType_beat_single5,
StepsType_beat_versus5,
StepsType_beat_double5,
StepsType_beat_single7,
StepsType_beat_versus7,
StepsType_beat_double7,
StepsType_maniax_single,
StepsType_maniax_double,
+91 -2
View File
@@ -69,8 +69,10 @@ static const StepsTypeInfo g_StepsTypeInfos[] = {
{ "ds3ddx-single", 8, true, StepsTypeCategory_Single },
// beatmania
{ "bm-single5", 6, true, StepsTypeCategory_Single }, // called "bm" for backward compat
{ "bm-versus5", 6, true, StepsTypeCategory_Single }, // called "bm" for backward compat
{ "bm-double5", 12, true, StepsTypeCategory_Double }, // called "bm" for backward compat
{ "bm-single7", 8, true, StepsTypeCategory_Single }, // called "bm" for backward compat
{ "bm-versus7", 8, true, StepsTypeCategory_Single }, // called "bm" for backward compat
{ "bm-double7", 16, true, StepsTypeCategory_Double }, // called "bm" for backward compat
// dance maniax
{ "maniax-single", 4, true, StepsTypeCategory_Single },
@@ -1584,7 +1586,47 @@ static const Style g_Style_Beat_Single5 =
false, // m_bLockDifficulties
};
static const Style g_Style_Beat_Double =
static const Style g_Style_Beat_Versus5 =
{ // STYLE_BEAT_VERSUS
true, // m_bUsedForGameplay
false, // m_bUsedForEdit
true, // m_bUsedForDemonstration
false, // m_bUsedForHowToPlay
"versus", // m_szName
StepsType_beat_versus5, // m_StepsType
StyleType_TwoPlayersTwoSides, // m_StyleType
6, // m_iColsPerPlayer
{ // m_ColumnInfo[NUM_PLAYERS][MAX_COLS_PER_PLAYER];
{ // PLAYER_1
{ TRACK_1, -BEAT_COL_SPACING*2.5f, NULL },
{ TRACK_2, -BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_3, -BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_4, +BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_5, +BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_6, +BEAT_COL_SPACING*3.0f, "scratch" },
},
{ // PLAYER_2
{ TRACK_1, -BEAT_COL_SPACING*2.5f, NULL },
{ TRACK_2, -BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_3, -BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_4, +BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_5, +BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_6, +BEAT_COL_SPACING*3.0f, "scratch" },
},
},
{ // m_iInputColumn[NUM_GameController][NUM_GameButton]
{ 0, 1, 2, 3, 4, Style::NO_MAPPING, Style::NO_MAPPING, 5, 5, Style::END_MAPPING },
{ 0, 1, 2, 3, 4, Style::NO_MAPPING, Style::NO_MAPPING, 5, 5, Style::END_MAPPING }
},
{ // m_iColumnDrawOrder[MAX_COLS_PER_PLAYER];
0,1,2,3,4,5
},
false, // m_bNeedsZoomOutWith2Players
false, // m_bCanUseBeginnerHelper
false, // m_bLockDifficulties
};
static const Style g_Style_Beat_Double5 =
{ // STYLE_BEAT_DOUBLE
true, // m_bUsedForGameplay
true, // m_bUsedForEdit
@@ -1680,6 +1722,51 @@ static const Style g_Style_Beat_Single7 =
false, // m_bLockDifficulties
};
static const Style g_Style_Beat_Versus7 =
{ // STYLE_BEAT_VERSUS7
true, // m_bUsedForGameplay
true, // m_bUsedForEdit
false, // m_bUsedForDemonstration
false, // m_bUsedForHowToPlay
"single7", // m_szName
StepsType_beat_versus7, // m_StepsType
StyleType_TwoPlayersTwoSides, // m_StyleType
8, // m_iColsPerPlayer
{ // m_ColumnInfo[NUM_PLAYERS][MAX_COLS_PER_PLAYER];
{ // PLAYER_1
{ TRACK_8, -BEAT_COL_SPACING*3.5f, "scratch" },
{ TRACK_1, -BEAT_COL_SPACING*2.0f, NULL },
{ TRACK_2, -BEAT_COL_SPACING*1.0f, NULL },
{ TRACK_3, -BEAT_COL_SPACING*0.0f, NULL },
{ TRACK_4, +BEAT_COL_SPACING*1.0f, NULL },
{ TRACK_5, +BEAT_COL_SPACING*2.0f, NULL },
{ TRACK_6, +BEAT_COL_SPACING*3.0f, NULL },
{ TRACK_7, +BEAT_COL_SPACING*4.0f, NULL },
},
{ // PLAYER_2
{ TRACK_1, -BEAT_COL_SPACING*3.5f, NULL },
{ TRACK_2, -BEAT_COL_SPACING*2.5f, NULL },
{ TRACK_3, -BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_4, -BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_5, +BEAT_COL_SPACING*0.5f, NULL },
{ TRACK_6, +BEAT_COL_SPACING*1.5f, NULL },
{ TRACK_7, +BEAT_COL_SPACING*2.5f, NULL },
{ TRACK_8, +BEAT_COL_SPACING*4.0f, "scratch" },
},
},
{ // m_iInputColumn[NUM_GameController][NUM_GameButton]
{ 1, 2, 3, 4, 5, 6, 7, 0, 0, Style::END_MAPPING },
{ 0, 1, 2, 3, 4, 5, 6, 7, 7, Style::END_MAPPING },
},
{ // m_iColumnDrawOrder[MAX_COLS_PER_PLAYER];
0,1,2,3,4,5,6,7
},
false, // m_bNeedsZoomOutWith2Players
false, // m_bCanUseBeginnerHelper
false, // m_bLockDifficulties
};
static const Style g_Style_Beat_Double7 =
{ // STYLE_BEAT_DOUBLE7
true, // m_bUsedForGameplay
@@ -1743,8 +1830,10 @@ static const Style g_Style_Beat_Double7 =
static const Style *g_apGame_Beat_Styles[] =
{
&g_Style_Beat_Single5,
&g_Style_Beat_Double,
&g_Style_Beat_Versus5,
&g_Style_Beat_Double5,
&g_Style_Beat_Single7,
&g_Style_Beat_Versus7,
&g_Style_Beat_Double7,
NULL
};
+16 -14
View File
@@ -146,7 +146,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing;
ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights )
@@ -156,7 +156,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing;
ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights )
@@ -201,7 +201,7 @@ static void StartMusic( MusicToPlay &ToPlay )
{
/* This song has no real timing data. The offset is arbitrary. Change it so
* the beat will line up to where we are now, so we don't have to delay. */
float fDestBeat = fmodfp( GAMESTATE->m_fSongBeatNoOffset, 1 );
float fDestBeat = fmodfp( GAMESTATE->m_Position.m_fSongBeatNoOffset, 1 );
float fTime = NewMusic->m_NewTiming.GetElapsedTimeFromBeatNoOffset( fDestBeat );
NewMusic->m_NewTiming.m_fBeat0OffsetInSeconds = fTime;
@@ -223,7 +223,7 @@ static void StartMusic( MusicToPlay &ToPlay )
* common when starting a precached sound, but our sound isn't, so it'll
* probably take a little longer. Nudge the latency up. */
const float fPresumedLatency = SOUNDMAN->GetPlayLatency() + 0.040f;
const float fCurSecond = GAMESTATE->m_fMusicSeconds + fPresumedLatency;
const float fCurSecond = GAMESTATE->m_Position.m_fMusicSeconds + fPresumedLatency;
const float fCurBeat = g_Playing->m_Timing.GetBeatFromElapsedTimeNoOffset( fCurSecond );
/* The beat that the new sound will start on. */
@@ -236,9 +236,9 @@ static void StartMusic( MusicToPlay &ToPlay )
const float fSecondToStartOn = g_Playing->m_Timing.GetElapsedTimeFromBeatNoOffset( fCurBeatToStartOn );
const float fMaximumDistance = 2;
const float fDistance = min( fSecondToStartOn - GAMESTATE->m_fMusicSeconds, fMaximumDistance );
const float fDistance = min( fSecondToStartOn - GAMESTATE->m_Position.m_fMusicSeconds, fMaximumDistance );
when = GAMESTATE->m_LastBeatUpdate + fDistance;
when = GAMESTATE->m_Position.m_LastBeatUpdate + fDistance;
}
/* Important: don't hold the mutex while we load and seek the actual sound. */
@@ -552,8 +552,8 @@ void GameSoundManager::Update( float fDeltaTime )
if( !g_Playing->m_Music->IsPlaying() )
{
/* There's no song playing. Fake it. */
CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_fMusicSeconds, fDeltaTime) );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_Position.m_fMusicSeconds, fDeltaTime) );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
return;
}
@@ -570,8 +570,8 @@ void GameSoundManager::Update( float fDeltaTime )
//
if( PREFSMAN->m_bLogSkips && !g_Playing->m_bTimingDelayed )
{
const float fExpectedTimePassed = (tm - GAMESTATE->m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate();
const float fSoundTimePassed = fSeconds - GAMESTATE->m_fMusicSeconds;
const float fExpectedTimePassed = (tm - GAMESTATE->m_Position.m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate();
const float fSoundTimePassed = fSeconds - GAMESTATE->m_Position.m_fMusicSeconds;
const float fDiff = fExpectedTimePassed - fSoundTimePassed;
static RString sLastFile = "";
@@ -580,7 +580,7 @@ void GameSoundManager::Update( float fDeltaTime )
/* If fSoundTimePassed < 0, the sound has probably looped. */
if( sLastFile == ThisFile && fSoundTimePassed >= 0 && fabsf(fDiff) > 0.003f )
LOG->Trace("Song position skip in %s: expected %.3f, got %.3f (cur %f, prev %f) (%.3f difference)",
Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_fMusicSeconds, fDiff );
Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_Position.m_fMusicSeconds, fDiff );
sLastFile = ThisFile;
}
@@ -599,7 +599,7 @@ void GameSoundManager::Update( float fDeltaTime )
{
/* We're still waiting for the new sound to start playing, so keep using the
* old timing data and fake the time. */
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
}
else
{
@@ -614,7 +614,7 @@ void GameSoundManager::Update( float fDeltaTime )
{
static int iBeatLastCrossed = 0;
float fSongBeat = GAMESTATE->m_fSongBeat;
float fSongBeat = GAMESTATE->m_Position.m_fSongBeat;
int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow );
@@ -638,7 +638,7 @@ void GameSoundManager::Update( float fDeltaTime )
NoteData &lights = g_Playing->m_Lights;
if( lights.GetNumTracks() > 0 ) // lights data was loaded
{
const float fSongBeat = GAMESTATE->m_fLightSongBeat;
const float fSongBeat = GAMESTATE->m_Position.m_fLightSongBeat;
const int iSongRow = BeatToNoteRowNotRounded( fSongBeat );
static int iRowLastCrossed = 0;
@@ -806,12 +806,14 @@ public:
return 0;
}
static int PlayOnce( T* p, lua_State *L ) { RString sPath = SArg(1); p->PlayOnce( sPath ); return 0; }
static int PlayAnnouncer( T* p, lua_State *L ) { RString sPath = SArg(1); p->PlayOnceFromAnnouncer( sPath ); return 0; }
static int GetPlayerBalance( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1); lua_pushnumber( L, p->GetPlayerBalance(pn) ); return 1; }
LunaGameSoundManager()
{
ADD_METHOD( DimMusic );
ADD_METHOD( PlayOnce );
ADD_METHOD( PlayAnnouncer );
ADD_METHOD( GetPlayerBalance );
}
};
+34 -67
View File
@@ -127,7 +127,9 @@ GameState::GameState() :
m_pEditSourceSteps( Message_EditSourceStepsChanged ),
m_stEditSource( Message_EditSourceStepsTypeChanged ),
m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ),
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged )
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ),
m_bIsUsingStepTiming( true ),
m_bInStepEditor( false )
{
g_pImpl = new GameStateImpl;
@@ -209,7 +211,7 @@ void GameState::ApplyCmdline()
RString sPlayer;
for( int i = 0; GetCommandlineArgument( "player", &sPlayer, i ); ++i )
{
int pn = atoi( sPlayer )-1;
int pn = StringToInt( sPlayer )-1;
if( !IsAnInt( sPlayer ) || pn < 0 || pn >= NUM_PLAYERS )
RageException::Throw( "Invalid argument \"--player=%s\".", sPlayer.c_str() );
@@ -893,22 +895,15 @@ const float GameState::MUSIC_SECONDS_INVALID = -5000.0f;
void GameState::ResetMusicStatistics()
{
m_fMusicSeconds = 0; // MUSIC_SECONDS_INVALID;
// todo: move me to FOREACH_EnabledPlayer( p ) after [NUM_PLAYERS]ing
m_fSongBeat = 0;
m_fSongBeatNoOffset = 0;
m_fCurBPS = 10;
//m_bStop = false;
m_bFreeze = false;
m_bDelay = false;
m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj
m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj
m_fMusicSecondsVisible = 0;
m_fSongBeatVisible = 0;
m_Position.Reset();
Actor::SetBGMTime( 0, 0, 0, 0 );
FOREACH_PlayerNumber( p )
{
m_pPlayerState[p]->ClearHopoState();
m_pPlayerState[p]->m_Position.Reset();
}
}
void GameState::ResetStageStatistics()
@@ -947,57 +942,23 @@ void GameState::ResetStageStatistics()
m_iStageSeed = rand();
}
static Preference<float> g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f );
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp, bool bUpdatePlayers )
{
if( !timestamp.IsZero() )
m_LastBeatUpdate = timestamp;
else
m_LastBeatUpdate.Touch();
m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp );
// xxx testing: only do this on monotune survivor
/*
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
LOG->Trace( ssprintf("[GameState::UpdateSongPosition] cur BPS = %f, fPositionSeconds = %f",m_fCurBPS,fPositionSeconds) );
*/
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination );
// "Crash reason : -243478.890625 -48695.773438"
ASSERT_M( m_fSongBeat > -2000, ssprintf("Song beat %f at %f seconds", m_fSongBeat, fPositionSeconds) );
m_fMusicSeconds = fPositionSeconds;
m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds );
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2;
bool bThrowAway;
int iThrowAway;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
/*
// xxx testing: only do this on monotune survivor
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
if( bUpdatePlayers )
{
// and only do it in the known negative bpm region. HACKITY HACK
if(m_fSongBeat >= 445.490f && m_fSongBeat <= 453.72f)
FOREACH_EnabledPlayer( pn )
{
LOG->Trace( ssprintf("fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
//LOG->Trace( ssprintf("Music seconds visible %f = fPositionSeconds %f - g_fVisualDelaySeconds %f", m_fMusicSecondsVisible,fPositionSeconds,g_fVisualDelaySeconds.Get()) );
}
else if(m_fSongBeat == 445.500f)
{
LOG->Trace( ssprintf("[beat 445.500] fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
if( m_pCurSteps[pn] )
{
m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp );
Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset );
}
}
}
*/
Actor::SetBGMTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fPositionSeconds, m_fSongBeatNoOffset );
Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset );
// LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze );
}
@@ -2291,11 +2252,16 @@ public:
DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() )
DEFINE_METHOD( IsEventMode, IsEventMode() )
DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() )
DEFINE_METHOD( GetSongBeat, m_fSongBeat )
DEFINE_METHOD( GetSongBeatVisible, m_fSongBeatVisible )
DEFINE_METHOD( GetSongBPS, m_fCurBPS )
DEFINE_METHOD( GetSongFreeze, m_bFreeze )
DEFINE_METHOD( GetSongDelay, m_bDelay )
/*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat )
DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible )
DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS )
DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze )
DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn )
DEFINE_METHOD( GetCoins, m_iCoins )
DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check<PlayerNumber>(L, 1)] )
@@ -2434,7 +2400,7 @@ public:
static int JoinPlayer( T* p, lua_State *L ) { p->JoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int UnjoinPlayer( T* p, lua_State *L ) { p->UnjoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int GetSongPercent( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongPercent(FArg(1))); return 1; }
DEFINE_METHOD( GetCurMusicSeconds, m_fMusicSeconds )
DEFINE_METHOD( GetCurMusicSeconds, m_Position.m_fMusicSeconds )
DEFINE_METHOD( GetWorkoutGoalComplete, m_bWorkoutGoalComplete )
static int GetCharacter( T* p, lua_State *L ) { p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)]->PushSelf(L); return 1; }
@@ -2500,11 +2466,12 @@ public:
ADD_METHOD( GetHardestStepsDifficulty );
ADD_METHOD( IsEventMode );
ADD_METHOD( GetNumPlayersEnabled );
ADD_METHOD( GetSongBeat );
/*ADD_METHOD( GetSongBeat );
ADD_METHOD( GetSongBeatVisible );
ADD_METHOD( GetSongBPS );
ADD_METHOD( GetSongFreeze );
ADD_METHOD( GetSongDelay );
ADD_METHOD( GetSongDelay );*/
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetGameplayLeadIn );
ADD_METHOD( GetCoins );
ADD_METHOD( IsSideJoined );
+44 -29
View File
@@ -10,6 +10,7 @@
#include "RageTimer.h"
#include "PlayerOptions.h"
#include "SongOptions.h"
#include "SongPosition.h"
#include "Preference.h"
#include <map>
@@ -43,7 +44,8 @@ public:
void ResetPlayer( PlayerNumber pn );
void ApplyCmdline(); // called by Reset
void ApplyGameCommand( const RString &sCommand, PlayerNumber pn=PLAYER_INVALID );
void BeginGame(); // called when first player joins
/** @brief Start the game when the first player joins in. */
void BeginGame();
void JoinPlayer( PlayerNumber pn );
void UnjoinPlayer( PlayerNumber pn );
bool JoinInput( PlayerNumber pn );
@@ -55,13 +57,25 @@ public:
bool HaveProfileToSave();
void SaveLocalData();
void LoadCurrentSettingsFromProfile( PlayerNumber pn );
void SaveCurrentSettingsToProfile( PlayerNumber pn ); // called at the beginning of each stage
/**
* @brief Save the specified player's settings to his/her profile.
*
* This is called at the beginning of each stage.
* @param pn the PlayerNumber to save the stats to. */
void SaveCurrentSettingsToProfile( PlayerNumber pn );
Song* GetDefaultSong() const;
void Update( float fDelta );
// Main state info
void SetCurGame( const Game *pGame ); // Call this instead of m_pCurGame.Set to make sure PREFSMAN->m_sCurrentGame stays in sync
/**
* @brief State what the current game is.
*
* Call this instead of m_pCurGame.Set to make sure that
* PREFSMAN->m_sCurrentGame stays in sync.
* @param pGame the game to start using. */
void SetCurGame( const Game *pGame );
BroadcastOnChangePtr<const Game> m_pCurGame;
BroadcastOnChangePtr<const Style> m_pCurStyle;
/** @brief Determine which side is joined.
@@ -129,6 +143,9 @@ public:
bool IsCourseMode() const;
bool IsBattleMode() const; // not Rave
/**
* @brief Do we show the W1 timing judgment?
* @return true if we do, or false otherwise. */
bool ShowW1() const;
BroadcastOnChange<RString> m_sPreferredSongGroup; // GROUP_ALL denotes no preferred group
@@ -141,7 +158,11 @@ public:
SortOrder m_PreferredSortOrder; // used by MusicWheel
EditMode m_EditMode;
bool IsEditing() const { return m_EditMode != EditMode_Invalid; }
bool m_bDemonstrationOrJukebox; // ScreenGameplay does special stuff when this is true
/**
* @brief Are we in the demonstration or jukebox mode?
*
* ScreenGameplay often does special things when this is set to true. */
bool m_bDemonstrationOrJukebox;
bool m_bJukeboxUsesModifiers;
int m_iNumStagesOfThisSong;
/**
@@ -194,29 +215,9 @@ public:
bool m_bBackedOutOfFinalStage;
// Music statistics:
// Arcade - the current stage (one song).
// Oni/Endless - a single song in a course.
// Let a lot of classes access this info here so they don't have to keep their own copies.
// todo: [NUM_PLAYERS] this for split bpm lolol -aj
float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat;
float m_fSongBeatNoOffset;
float m_fCurBPS;
float m_fLightSongBeat; // g_fLightsFalloffSeconds ahead
//bool m_bStop; // in the middle of a stop (freeze or delay)
/** @brief A flag to determine if we're in the middle of a freeze/stop. */
bool m_bFreeze;
/** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */
bool m_bDelay;
/** @brief The row used to start a warp. */
int m_iWarpBeginRow;
/** @brief The beat to warp to afterwards. */
float m_fWarpDestination;
RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update
BroadcastOnChange<bool> m_bGameplayLeadIn;
SongPosition m_Position;
float m_fMusicSecondsVisible;
float m_fSongBeatVisible;
BroadcastOnChange<bool> m_bGameplayLeadIn;
// if re-adding noteskin changes in courses, add functions and such here -aj
void GetAllUsedNoteSkins( vector<RString> &out ) const;
@@ -224,7 +225,7 @@ public:
static const float MUSIC_SECONDS_INVALID;
void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above.
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer );
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer, bool bUpdatePlayers = false );
float GetSongPercent( float beat ) const;
bool AllAreInDangerOrWorse() const;
@@ -259,8 +260,11 @@ public:
// Options stuff
ModsGroup<SongOptions> m_SongOptions;
// True if the current mode has changed the default NoteSkin, such as Edit/Sync Songs does.
// Note: any mode that wants to use it must set it
/**
* @brief Did the current game mode change the default Noteskin?
*
* This is true if it has: see Edit/Sync Songs for a common example.
* Note: any mode that wants to use this must set it explicitly. */
bool m_bDidModeChangeNoteSkin;
void GetDefaultPlayerOptions( PlayerOptions &po );
@@ -327,6 +331,17 @@ public:
Premium GetPremium() const;
// Edit stuff
/**
* @brief Is the game right now using Song timing or Steps timing?
*
* Different options are available depending on this setting. */
bool m_bIsUsingStepTiming;
/**
* @brief Are we presently in the Step Editor, where some rules apply differently?
*
* TODO: Find a better way to implement this. */
bool m_bInStepEditor;
BroadcastOnChange<StepsType> m_stEdit;
BroadcastOnChange<CourseDifficulty> m_cdEdit;
BroadcastOnChangePtr<Steps> m_pEditSourceSteps;
+6 -6
View File
@@ -25,9 +25,9 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
* will start coming out the speaker. Compensate for this by boosting fPositionSeconds
* ahead. This is just to make sure that we request the sound early enough for it to
* come out on time; the actual precise timing is handled by SetStartTime. */
float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f;
const TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
const TimingData &timing = GAMESTATE->m_pCurSong->m_SongTiming;
const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) );
@@ -47,11 +47,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{
const float fTickBeat = NoteRowToBeat( iClapRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds;
float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
m_soundAssistClap.Play( &p );
}
}
@@ -83,11 +83,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{
const float fTickBeat = NoteRowToBeat( iMetronomeRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds;
float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
if( bIsMeasure )
m_soundAssistMetronomeMeasure.Play( &p );
else
+1 -1
View File
@@ -948,7 +948,7 @@ MultiPlayer InputMapper::InputDeviceToMultiPlayer( InputDevice id )
GameButton InputScheme::ButtonNameToIndex( const RString &sButtonName ) const
{
for( GameButton gb=(GameButton) 0; gb<m_iButtonsPerController; gb=(GameButton)(gb+1) )
if( stricmp(GetGameButtonName(gb), sButtonName) == 0 )
if( sButtonName.EqualsNoCase(GetGameButtonName(gb)) )
return gb;
return GameButton_Invalid;
+1 -1
View File
@@ -121,7 +121,7 @@ void Inventory::Update( float fDelta )
// use items if this player is CPU-controlled
if( m_pPlayerState->m_PlayerController != PC_HUMAN &&
GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
{
// every 1 seconds, try to use an item
int iLastSecond = (int)(RageTimer::GetTimeSinceStartFast() - fDelta);
+2 -2
View File
@@ -4,8 +4,8 @@
#include "RageUtil.h"
#include "RageLog.h"
#include "arch/Dialog/Dialog.h"
#include "../extern/jsoncpp/include/json/reader.h"
#include "../extern/jsoncpp/include/json/writer.h"
#include "json/reader.h"
#include "json/writer.h"
bool JsonUtil::LoadFromString(Json::Value &root, RString sData, RString &sErrorOut)
{
+184 -2
View File
@@ -1,9 +1,10 @@
/** @brief Utilities for handling JSON data. */
#ifndef JsonUtil_H
#define JsonUtil_H
class RageFileBasic;
#include "../extern/jsoncpp/include/json/value.h"
/** @brief Utilities for handling JSON data. */
#include "json/value.h"
namespace JsonUtil
{
bool LoadFromString( Json::Value &root, RString sData, RString &sErrorOut );
@@ -31,6 +32,86 @@ namespace JsonUtil
fn(*v[i], root[i]);
}
template<typename V, typename T>
static void SerializeArray(const V &v, void fn(const T &, Json::Value &), Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize( v.size() );
int i=0;
for( typename V::const_iterator iter=v.begin(); iter!=v.end(); iter++ )
fn( *iter, root[i++] );
}
template <typename V>
static void SerializeArrayValues(const V &v, Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize( v.size() );
int i=0;
for( typename V::const_iterator iter=v.begin(); iter!=v.end(); iter++ )
root[i++] = *iter;
}
template <typename V>
static void SerializeArrayObjects(const V &v, Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize( v.size() );
int i=0;
for( typename V::const_iterator iter=v.begin(); iter!=v.end(); iter++ )
iter->Serialize( root[i++] );
}
template <typename M, typename E, typename F>
static void SerializeStringToObjectMap(const M &m, F fnEnumToString(E e), Json::Value &root)
{
for( typename M::const_iterator iter=m.begin(); iter!=m.end(); iter++ )
iter->second.Serialize( root[ fnEnumToString(iter->first) ] );
}
template <typename M, typename E, typename F>
static void SerializeStringToValueMap(const M &m, F fnToString(E e), Json::Value &root)
{
for( typename M::const_iterator iter=m.begin(); iter!=m.end(); iter++ )
root[ fnToString(iter->first) ] = iter->second;
}
template <typename M>
static void SerializeValueToValueMap(const M &m, Json::Value &root)
{
for( typename M::const_iterator iter=m.begin(); iter!=m.end(); iter++ )
root[ (iter->first) ] = iter->second;
}
// Serialize a map that has a non-string key type
template <typename V>
static void SerializeObjectToObjectMapAsArray(const V &v, const RString &sKeyName, const RString &sValueName, Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize( v.size() );
int i=0;
for( typename V::const_iterator iter=v.begin(); iter!=v.end(); iter++ )
{
Json::Value &vv = root[i++];
iter->first.Serialize( vv[sKeyName] );
iter->second.Serialize( vv[sValueName] );
}
}
template <typename V>
static void SerializeObjectToValueMapAsArray(const V &v, const RString &sKeyName, const RString &sValueName, Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize( v.size() );
int i=0;
for( typename V::const_iterator iter=v.begin(); iter!=v.end(); iter++ )
{
Json::Value &vv = root[i++];
iter->first.Serialize( vv[sKeyName] );
vv[sValueName] = iter->second;
}
}
template<class T>
static void SerializeVectorValues(const vector<T> &v, Json::Value &root)
{
@@ -48,6 +129,14 @@ namespace JsonUtil
fn(v[i], root[i]);
}
template <typename V>
static void DeserializeArrayObjects( V &v, const Json::Value &root)
{
v.resize( root.size() );
for( unsigned i=0; i<v.size(); i++ )
v[i].Deserialize( root[i] );
}
template<class T>
static void DeserializeVectorPointers(vector<T*> &v, void fn(T &, const Json::Value &), const Json::Value &root)
{
@@ -61,6 +150,99 @@ namespace JsonUtil
}
}
template<class T>
static void DeserializeArrayValues(vector<T> &v, const Json::Value &root)
{
v.clear();
for( unsigned i=0; i<root.size(); i++ )
{
T t;
if( root[i].TryGet( t ) )
v.push_back( t );
}
}
// don't pull in the set header here
template<typename S, typename T>
static void DeserializeArrayValuesIntoSet(S &s, const Json::Value &root)
{
s.clear();
for( unsigned i=0; i<root.size(); i++ )
{
T t;
if( root[i].TryGet( t ) )
s.insert( t );
}
}
template<typename T>
static void DeserializeArrayValuesIntoVector(vector<T> &v, const Json::Value &root)
{
v.clear();
for( unsigned i=0; i<root.size(); i++ )
{
T t;
if( root[i].TryGet( t ) )
v.push_back( t );
}
}
template <typename M>
static void DeserializeValueToValueMap(M &m, const Json::Value &root)
{
for( Json::Value::const_iterator iter = root.begin(); iter != root.end(); iter++ )
(*iter).TryGet( m[ iter.memberName() ] );
}
template <typename M, typename E, typename F>
static void DeserializeStringToValueMap(M &m, F fnToValue(E e), const Json::Value &root)
{
for( Json::Value::const_iterator iter = root.begin(); iter != root.end(); iter++ )
(*iter).TryGet( m[ fnToValue(iter.memberName()) ] );
}
template <typename M, typename E, typename F>
static void DeserializeStringToObjectMap(M &m, F fnToValue(E e), const Json::Value &root)
{
for( Json::Value::const_iterator iter = root.begin(); iter != root.end(); iter++ )
m[ fnToValue(iter.memberName()) ].Deserialize( *iter );
}
// Serialize a map that has a non-string key type
template <typename K, typename V>
static void DeserializeObjectToObjectMapAsArray(map<K,V> &m, const RString &sKeyName, const RString &sValueName, const Json::Value &root)
{
m.clear();
ASSERT( root.type() == Json::arrayValue );
for( Json::Value::const_iterator iter = root.begin(); iter != root.end(); iter++ )
{
ASSERT( (*iter).type() == Json::objectValue );
K k;
if( !k.Deserialize( (*iter)[sKeyName] ) )
continue;
V v;
if( !v.Deserialize( (*iter)[sValueName] ) )
continue;
m[k] = v;
}
}
template <typename K, typename V>
static void DeserializeObjectToValueMapAsArray(map<K,V> &m, const RString &sKeyName, const RString &sValueName, const Json::Value &root)
{
for( unsigned i=0; i<root.size(); i++ )
{
const Json::Value &root2 = root[i];
K k;
if( !k.Deserialize( root2[sKeyName] ) )
continue;
V v;
if( !root2[sValueName].TryGet(v) )
continue;
m[k] = v;
}
}
template<class T>
static void DeserializeVectorValues(vector<T> &v, const Json::Value &root)
{
+3 -3
View File
@@ -230,12 +230,12 @@ void LightsManager::Update( float fDeltaTime )
static float fLastBeat;
static int iLight;
if( fracf(GAMESTATE->m_fLightSongBeat) < fracf(fLastBeat) )
if( fracf(GAMESTATE->m_Position.m_fLightSongBeat) < fracf(fLastBeat) )
{
++iLight;
wrap( iLight, 4 );
}
fLastBeat = GAMESTATE->m_fLightSongBeat;
fLastBeat = GAMESTATE->m_Position.m_fLightSongBeat;
switch( iLight )
{
case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break;
@@ -405,7 +405,7 @@ void LightsManager::Update( float fDeltaTime )
// If not joined, has enough credits, and not too late to join, then
// blink the menu buttons rapidly so they'll press Start
{
int iBeat = (int)(GAMESTATE->m_fLightSongBeat*4);
int iBeat = (int)(GAMESTATE->m_Position.m_fLightSongBeat*4);
bool bBlinkOn = (iBeat%2)==0;
FOREACH_PlayerNumber( pn )
{
+1 -1
View File
@@ -11,7 +11,7 @@
#include "Command.h"
#include "RageTypes.h"
#include <sstream>
#include <sstream> // conversion for lua functions.
#include <csetjmp>
#include <cassert>
#include <map>
+4 -4
View File
@@ -41,9 +41,9 @@ void LyricDisplay::Update( float fDeltaTime )
return;
// If the song has changed (in a course), reset.
if( GAMESTATE->m_fMusicSeconds < m_fLastSecond )
if( GAMESTATE->m_Position.m_fMusicSeconds < m_fLastSecond )
Init();
m_fLastSecond = GAMESTATE->m_fMusicSeconds;
m_fLastSecond = GAMESTATE->m_Position.m_fMusicSeconds;
if( m_iCurLyricNumber >= GAMESTATE->m_pCurSong->m_LyricSegments.size() )
return;
@@ -51,7 +51,7 @@ void LyricDisplay::Update( float fDeltaTime )
const Song *pSong = GAMESTATE->m_pCurSong;
const float fStartTime = (pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime) - IN_LENGTH.GetValue();
if( GAMESTATE->m_fMusicSeconds < fStartTime )
if( GAMESTATE->m_Position.m_fMusicSeconds < fStartTime )
return;
// Clamp this lyric to the beginning of the next or the end of the music.
@@ -59,7 +59,7 @@ void LyricDisplay::Update( float fDeltaTime )
if( m_iCurLyricNumber+1 < GAMESTATE->m_pCurSong->m_LyricSegments.size() )
fEndTime = pSong->m_LyricSegments[m_iCurLyricNumber+1].m_fStartTime;
else
fEndTime = pSong->GetElapsedTimeFromBeat( pSong->m_fLastBeat );
fEndTime = pSong->m_SongTiming.GetElapsedTimeFromBeat( pSong->m_fLastBeat );
const float fDistance = fEndTime - pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime;
const float fTweenBufferTime = IN_LENGTH.GetValue() + OUT_LENGTH.GetValue();
+1 -1
View File
@@ -63,7 +63,7 @@ bool LyricsLoader::LoadFromLRCFile(const RString& sPath, Song& out)
StripCrnl(sValueData);
// handle the data
if( 0==stricmp(sValueName,"COLOUR") || 0==stricmp(sValueName,"COLOR") )
if( sValueName.EqualsNoCase("COLOUR") || sValueName.EqualsNoCase("COLOR") )
{
// set color var here for this segment
int r, g, b;
+49 -18
View File
@@ -42,6 +42,19 @@ increment_version:
ver.cpp:
$(MAKE) increment_version
PNG = \
../extern/libpng/include/png.c ../extern/libpng/include/.png.h \
../extern/libpng/include/pngconf.h ../extern/libpng/include/pngdebug.h \
../extern/libpng/include/pngerror.c ../extern/libpng/include/pngget.c \
../extern/libpng/include/pnginfo.h ../extern/libpng/include/scripts/pnglibconf.h \
../extern/libpng/include/pngmem.c ../extern/libpng/include/pngpread.c \
../extern/libpng/include/pngpriv.h ../extern/libpng/include/pngread.c \
../extern/libpng/include/pngrio.c ../extern/libpng/include/pngrtran.c \
../extern/libpng/include/pngrutil.c ../extern/libpng/include/pngset.c \
../extern/libpng/include/pngstruct.h ../extern/libpng/include/pngtrans.c \
../extern/libpng/include/pngwio.c ../extern/libpng/include/pngwrite.c \
../extern/libpng/include/pngwtran.c ../extern/libpng/include/pngwutil.c
Screens = \
Screen.cpp Screen.h ScreenAttract.cpp ScreenAttract.h \
ScreenBookkeeping.cpp ScreenBookkeeping.h \
@@ -145,10 +158,12 @@ ModsGroup.cpp ModsGroup.h \
NoteData.cpp NoteData.h NoteDataUtil.cpp NoteDataUtil.h NoteDataWithScoring.cpp NoteDataWithScoring.h \
NoteTypes.cpp NoteTypes.h NotesLoader.cpp NotesLoader.h \
NotesLoaderBMS.cpp NotesLoaderBMS.h NotesLoaderDWI.cpp NotesLoaderDWI.h \
NotesLoaderJson.cpp NotesLoaderJson.h \
NotesLoaderKSF.cpp NotesLoaderKSF.h NotesLoaderMidi.cpp NotesLoaderMidi.h \
NotesLoaderPMS.cpp NotesLoaderPMS.h NotesLoaderSM.cpp NotesLoaderSM.h \
NotesLoaderSSC.cpp NotesLoaderSSC.h NotesLoaderSMA.cpp NotesLoaderSMA.h \
NotesWriterDWI.cpp NotesWriterDWI.h \
NotesWriterJson.cpp NotesWriterJson.h \
NotesWriterSM.cpp NotesWriterSM.h NotesWriterSSC.cpp NotesWriterSSC.h \
OptionRowHandler.cpp OptionRowHandler.h OptionsList.cpp OptionsList.h \
PlayerAI.cpp PlayerAI.h PlayerNumber.cpp PlayerNumber.h PlayerOptions.cpp PlayerOptions.h \
@@ -358,24 +373,34 @@ ScoreDisplayBattle.cpp ScoreDisplayBattle.h \
ScoreDisplayCalories.cpp ScoreDisplayCalories.h \
ScoreDisplayLifeTime.cpp ScoreDisplayLifeTime.h \
ScoreDisplayNormal.cpp ScoreDisplayNormal.h ScoreDisplayOni.cpp ScoreDisplayOni.h \
ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h
ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h \
SongPosition.cpp SongPosition.h
PCRE = pcre/get.c pcre/internal.h pcre/maketables.c pcre/pcre.c pcre/pcre.h pcre/study.c
EXTRA_DIST += pcre/chartables.c
PCRE = ../extern/pcre/get.c ../extern/pcre/internal.h ../extern/pcre/maketables.c ../extern/pcre/pcre.c ../extern/pcre/pcre.h ../extern/pcre/study.c
EXTRA_DIST += ../extern/pcre/chartables.c
Lua = lua-5.1/src/lapi.c lua-5.1/src/lauxlib.c lua-5.1/src/lbaselib.c lua-5.1/src/lcode.c lua-5.1/src/ldblib.c \
lua-5.1/src/ldebug.c lua-5.1/src/ldo.c lua-5.1/src/ldump.c lua-5.1/src/lfunc.c lua-5.1/src/lgc.c lua-5.1/src/linit.c \
lua-5.1/src/liolib.c lua-5.1/src/llex.c lua-5.1/src/lmathlib.c lua-5.1/src/lmem.c lua-5.1/src/loadlib.c \
lua-5.1/src/lobject.c lua-5.1/src/lopcodes.c lua-5.1/src/loslib.c lua-5.1/src/lparser.c lua-5.1/src/lstate.c \
lua-5.1/src/lstring.c lua-5.1/src/lstrlib.c lua-5.1/src/ltable.c lua-5.1/src/ltablib.c lua-5.1/src/ltm.c \
lua-5.1/src/lundump.c lua-5.1/src/lvm.c lua-5.1/src/lzio.c lua-5.1/src/lapi.h lua-5.1/src/lauxlib.h lua-5.1/src/lcode.h \
lua-5.1/src/ldebug.h lua-5.1/src/ldo.h lua-5.1/src/lfunc.h lua-5.1/src/lgc.h lua-5.1/src/llex.h lua-5.1/src/llimits.h \
lua-5.1/src/lmem.h lua-5.1/src/lobject.h lua-5.1/src/lopcodes.h lua-5.1/src/lparser.h lua-5.1/src/lstate.h \
lua-5.1/src/lstring.h lua-5.1/src/ltable.h lua-5.1/src/ltm.h lua-5.1/src/luaconf.h lua-5.1/src/lua.h lua-5.1/src/lualib.h \
lua-5.1/src/lundump.h lua-5.1/src/lvm.h lua-5.1/src/lzio.h
Lua = ../extern/lua-5.1/src/lapi.c ../extern/lua-5.1/src/lauxlib.c ../extern/lua-5.1/src/lbaselib.c ../extern/lua-5.1/src/lcode.c ../extern/lua-5.1/src/ldblib.c \
../extern/lua-5.1/src/ldebug.c ../extern/lua-5.1/src/ldo.c ../extern/lua-5.1/src/ldump.c ../extern/lua-5.1/src/lfunc.c ../extern/lua-5.1/src/lgc.c ../extern/lua-5.1/src/linit.c \
../extern/lua-5.1/src/liolib.c ../extern/lua-5.1/src/llex.c ../extern/lua-5.1/src/lmathlib.c ../extern/lua-5.1/src/lmem.c ../extern/lua-5.1/src/loadlib.c \
../extern/lua-5.1/src/lobject.c ../extern/lua-5.1/src/lopcodes.c ../extern/lua-5.1/src/loslib.c ../extern/lua-5.1/src/lparser.c ../extern/lua-5.1/src/lstate.c \
../extern/lua-5.1/src/lstring.c ../extern/lua-5.1/src/lstrlib.c ../extern/lua-5.1/src/ltable.c ../extern/lua-5.1/src/ltablib.c ../extern/lua-5.1/src/ltm.c \
../extern/lua-5.1/src/lundump.c ../extern/lua-5.1/src/lvm.c ../extern/lua-5.1/src/lzio.c ../extern/lua-5.1/src/lapi.h ../extern/lua-5.1/src/lauxlib.h ../extern/lua-5.1/src/lcode.h \
../extern/lua-5.1/src/ldebug.h ../extern/lua-5.1/src/ldo.h ../extern/lua-5.1/src/lfunc.h ../extern/lua-5.1/src/lgc.h ../extern/lua-5.1/src/llex.h ../extern/lua-5.1/src/llimits.h \
../extern/lua-5.1/src/lmem.h ../extern/lua-5.1/src/lobject.h ../extern/lua-5.1/src/lopcodes.h ../extern/lua-5.1/src/lparser.h ../extern/lua-5.1/src/lstate.h \
../extern/lua-5.1/src/lstring.h ../extern/lua-5.1/src/ltable.h ../extern/lua-5.1/src/ltm.h ../extern/lua-5.1/src/luaconf.h ../extern/lua-5.1/src/lua.h ../extern/lua-5.1/src/lualib.h \
../extern/lua-5.1/src/lundump.h ../extern/lua-5.1/src/lvm.h ../extern/lua-5.1/src/lzio.h
jsoncpp = jsoncpp/src/lib_json/json_reader.cpp jsoncpp/src/lib_json/json_value.cpp \
jsoncpp/src/lib_json/json_writer.cpp
jsoncpp = ../extern/jsoncpp/src/lib_json/json_reader.cpp \
../extern/jsoncpp/src/lib_json/json_value.cpp \
../extern/jsoncpp/src/lib_json/json_writer.cpp \
../extern/jsoncpp/include/json/autolink.h \
../extern/jsoncpp/include/json/config.h \
../extern/jsoncpp/include/json/features.h \
../extern/jsoncpp/include/json/forwards.h \
../extern/jsoncpp/include/json/json.h \
../extern/jsoncpp/include/json/reader.h \
../extern/jsoncpp/include/json/value.h \
../extern/jsoncpp/include/json/writer.h
RageFile = \
RageFileBasic.cpp RageFileBasic.h \
@@ -579,7 +604,11 @@ libtomcrypt_a_CPPFLAGS = -I$(srcdir)/libtomcrypt/src/headers $(AM_CPPFLAGS)
noinst_LIBRARIES += libtomcrypt.a
main_SOURCES = $(Screens) \
main_CPPFLAGS = -I$(top_srcdir)/extern/jsoncpp/include \
-I$(top_srcdir)/extern/glew-1.5.8/include
main_SOURCES = $(PNG) \
$(Screens) \
$(DataStructures) \
$(FileTypes) \
$(StepMania) \
@@ -597,15 +626,17 @@ main_LDADD = \
libtomcrypt.a libtommath.a
nodist_stepmania_SOURCES = ver.cpp
stepmania_CPPFLAGS = $(main_CPPFLAGS)
stepmania_SOURCES = $(main_SOURCES)
stepmania_LDADD = $(main_LDADD)
if BUILD_LUA_BINARIES
noinst_PROGRAMS += lua luac
lua_SOURCES = $(Lua) lua-5.1/src/lua.c
lua_SOURCES = $(Lua) ../extern/lua-5.1/src/lua.c
lua_LDADD = -lreadline
luac_SOURCES = $(Lua) lua-5.1/src/luac.c lua-5.1/src/print.c
luac_SOURCES = $(Lua) ../extern/lua-5.1/src/luac.c ../extern/lua-5.1/src/print.c
luac_LDADD =
endif
+3 -3
View File
@@ -73,9 +73,9 @@ void SongMeterDisplay::Update( float fDeltaTime )
{
if( GAMESTATE->m_pCurSong )
{
float fSongStartSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat );
float fSongEndSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fPercentPositionSong = SCALE( GAMESTATE->m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
float fSongStartSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat );
float fSongEndSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fPercentPositionSong = SCALE( GAMESTATE->m_Position.m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
CLAMP( fPercentPositionSong, 0, 1 );
SetPercent( fPercentPositionSong );
+3 -13
View File
@@ -39,12 +39,11 @@ void ModIcon::Load( RString sMetricsGroup )
this->AddChild( &m_text );
CROP_TEXT_TO_WIDTH.Load( sMetricsGroup, "CropTextToWidth" );
// stop words
/*
STOP_WORDS.Load( sMetricsGroup, "StopWords" );
m_vStopWords.empty();
split(STOP_WORDS, ",", m_vStopWords);
*/
Set("");
}
@@ -53,17 +52,8 @@ void ModIcon::Set( const RString &_sText )
{
RString sText = _sText;
// todo: make these metricable -aj
static const RString sStopWords[] =
{
"1X",
"DEFAULT",
"OVERHEAD",
"OFF",
};
for( unsigned i=0; i<ARRAYLEN(sStopWords); i++ )
if( 0==stricmp(sText,sStopWords[i]) )
for( unsigned i = 0; i < m_vStopWords.size(); i++ )
if( sText.EqualsNoCase(m_vStopWords[i]) )
sText = "";
sText.Replace( " ", "\n" );
+2
View File
@@ -21,6 +21,8 @@ protected:
AutoActor m_sprEmpty;
ThemeMetric<int> CROP_TEXT_TO_WIDTH;
ThemeMetric<RString> STOP_WORDS;
vector<RString> m_vStopWords;
};
#endif
+26 -1
View File
@@ -66,10 +66,35 @@ void ModIconRow::HandleMessage( const Message &msg )
struct OptionColumnEntry
{
char szString[30];
char *szString;
int iSlotIndex;
//void FromStack( lua_State *L, int iPos );
};
/*
void OptionColumnEntry::FromStack( lua_State *L, int iPos )
{
if( lua_type(L, iPos) != LUA_TTABLE )
return;
lua_pushvalue( L, iPos );
const int iTab = lua_gettop( L );
// option name
lua_getfield( L, iTab, "Name" );
RString sName = lua_tostring( L, -1 );
szString = const_cast<char *>(sName.c_str());
lua_settop( L, iTab );
// option icon index
lua_getfield( L, iTab, "IconIndex" );
iSlotIndex = lua_tointeger( L, -1 );
lua_settop( L, iTab );
}
static vector<OptionColumnEntry> g_OptionColumnEntries;
*/
// todo: metric these? -aj
static const OptionColumnEntry g_OptionColumnEntries[] =
{
-2
View File
@@ -22,9 +22,7 @@ public:
virtual void HandleMessage( const Message &msg );
//
// Commands
//
virtual void PushSelf( lua_State *L );
protected:
+2 -1
View File
@@ -466,7 +466,8 @@ int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = GetTapNote(t, r);
if( tn.type != TapNote::empty && tn.type != TapNote::mine && tn.type != TapNote::fake )
if( tn.type != TapNote::empty && tn.type != TapNote::mine
&& tn.type != TapNote::lift && tn.type != TapNote::fake )
iNumNotes++;
}
}
+2 -2
View File
@@ -2215,7 +2215,7 @@ void NoteDataUtil::AddTapAttacks( NoteData &nd, Song* pSong )
for( float sec=15; sec<pSong->m_fMusicLengthSeconds; sec+=30 )
{
float fBeat = pSong->GetBeatFromElapsedTime( sec );
float fBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( sec );
int iBeat = (int)fBeat;
int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track
TapNote tn(
@@ -2448,7 +2448,7 @@ void NoteDataUtil::SetHopoPossibleFlags( const Song *pSong, NoteData& ndInOut )
FOREACH_NONEMPTY_ROW_ALL_TRACKS( ndInOut, r )
{
float fBeat = NoteRowToBeat( r );
float fSeconds = pSong->GetElapsedTimeFromBeat( fBeat );
float fSeconds = pSong->m_SongTiming.GetElapsedTimeFromBeat( fBeat );
int iLastTapTrack = ndInOut.GetLastTrackWithTapOrHoldHead( r );
if( iLastTapTrack != -1 && fSeconds <= fLastRowMusicSeconds + g_fTimingWindowHopo )
+1 -1
View File
@@ -286,7 +286,7 @@ void NoteDisplay::Update( float fDeltaTime )
void NoteDisplay::SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLength, bool bVivid )
{
/* -inf ... inf */
float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? GAMESTATE->m_fSongBeat : GAMESTATE->m_fMusicSeconds;
float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? m_pPlayerState->m_Position.m_fSongBeat : m_pPlayerState->m_Position.m_fMusicSeconds;
/* -len ... +len */
float fPercentIntoAnimation = fmodf( fBeatOrSecond, fAnimationLength );
/* -1 ... 1 */
+221 -89
View File
@@ -19,6 +19,7 @@
#include "BackgroundUtil.h"
#include "Course.h"
#include "NoteData.h"
#include "Actor.h"
static ThemeMetric<bool> SHOW_BOARD( "NoteField", "ShowBoard" );
static ThemeMetric<bool> SHOW_BEAT_BARS( "NoteField", "ShowBeatBars" );
@@ -32,6 +33,33 @@ static ThemeMetric<float> FADE_FAIL_TIME( "NoteField", "FadeFailTime" );
static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); }
static ThemeMetric1D<RString> ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS );
inline const TimingData *GetRealTiming(const PlayerState *pPlayerState)
{
if( GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber] != NULL )
return &GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber]->m_Timing;
return NULL;
}
inline const TimingData *GetDisplayedTiming(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_pCurSong->m_SongTiming;
return GetRealTiming(pPlayerState);
}
inline const SongPosition *GetRealPosition(const PlayerState *pPlayerState)
{
return &pPlayerState->m_Position;
}
inline const SongPosition *GetDisplayedPosition(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_Position;
return GetRealPosition(pPlayerState);
}
NoteField::NoteField()
{
m_pNoteData = NULL;
@@ -240,7 +268,7 @@ void NoteField::Update( float fDeltaTime )
ActorFrame::Update( fDeltaTime );
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
const float fCurrentBeat = GAMESTATE->m_fSongBeat;
const float fCurrentBeat = GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK
if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 )
{
@@ -427,6 +455,8 @@ static ThemeMetric<RageColor> TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignature
static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" );
static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" );
static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" );
static ThemeMetric<RageColor> SPEED_COLOR ( "NoteField", "SpeedColor" );
static ThemeMetric<RageColor> FAKE_COLOR ("NoteField", "FakeColor" );
static ThemeMetric<bool> BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" );
static ThemeMetric<bool> STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" );
static ThemeMetric<bool> DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" );
@@ -435,6 +465,8 @@ static ThemeMetric<bool> TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatu
static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" );
static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" );
static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" );
static ThemeMetric<bool> SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" );
static ThemeMetric<bool> FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" );
static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" );
static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" );
static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" );
@@ -443,6 +475,8 @@ static ThemeMetric<float> TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOf
static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" );
static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" );
static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" );
static ThemeMetric<float> SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" );
static ThemeMetric<float> FAKE_OFFSETX ( "NoteField", "FakeOffsetX" );
void NoteField::DrawBPMText( const float fBeat, const float fBPM )
{
@@ -572,6 +606,40 @@ void NoteField::DrawLabelText( const float fBeat, RString sLabel )
m_textMeasureNumber.Draw();
}
void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = SPEED_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( SPEED_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f\n%s\n%.3f", fPercent, (usMode == 1 ? "S" : "B"), fWait) );
m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawFakeText( const float fBeat, const float fNewBeat )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = FAKE_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( FAKE_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f", fNewBeat) );
m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawAttackText( const float fBeat, const Attack &attack )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
@@ -606,7 +674,7 @@ void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName )
// change this probing to binary search
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels )
{
float fFirstBeatToDraw = GAMESTATE->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
float fFirstBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
/* In Boomerang, we'll usually have two sections of notes: before and after
* the peak. We always start drawing before the peak, and end after it, or
@@ -618,7 +686,7 @@ float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistance
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
}
while( fFirstBeatToDraw < GAMESTATE->m_fSongBeat )
while( fFirstBeatToDraw < GetDisplayedPosition(pPlayerState)->m_fSongBeat )
{
bool bIsPastPeakYOffset;
float fPeakYOffset;
@@ -640,7 +708,7 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
// Probe for last note to draw. Worst case is 0.25x + boost.
// Adjust search distance so that notes don't pop onto the screen.
float fSearchDistance = 10;
float fLastBeatToDraw = GAMESTATE->m_fSongBeat+fSearchDistance;
float fLastBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat+fSearchDistance;
const int NUM_ITERATIONS = 20;
@@ -669,6 +737,19 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
return fLastBeatToDraw;
}
inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
{
/*
if( GAMESTATE->m_bIsUsingStepTiming )
{
*/
return NoteRowToBeat(iRow);
/*
}
return GetDisplayedTiming(pPlayerState)->GetBeatFromElapsedTime(GetRealTiming(pPlayerState)->GetElapsedTimeFromBeat(NoteRowToBeat(iRow)));
*/
}
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
{
// TRICKY: If boomerang is on, then ones in the range
@@ -685,6 +766,12 @@ bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargets
void NoteField::DrawPrimitives()
{
// XXX Hack: Set Actor's active player number so the notes get the flashing that matches the steps.
// save the active player number (so they can nest)
PlayerNumber pnLastActivePlayerNumber = m_pPlayerState->m_PlayerNumber;
Actor::m_ActivePlayerNumber = m_pPlayerState->m_PlayerNumber;
//LOG->Trace( "NoteField::DrawPrimitives()" );
// This should be filled in on the first update.
@@ -739,10 +826,13 @@ void NoteField::DrawPrimitives()
cur->m_ReceptorArrowRow.Draw();
}
const TimingData *pTiming = GetDisplayedTiming(m_pPlayerState);
// Draw beat bars
if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS )
if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
{
const vector<TimeSignatureSegment> &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments;
const TimingData &timing = *pTiming;
const vector<TimeSignatureSegment> &vTimeSignatureSegments = timing.m_vTimeSignatureSegments;
int iMeasureIndex = 0;
FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter )
{
@@ -781,12 +871,14 @@ void NoteField::DrawPrimitives()
}
}
if( GAMESTATE->IsEditing() )
if( GAMESTATE->IsEditing() && pTiming != NULL )
{
ASSERT(GAMESTATE->m_pCurSong);
const TimingData &timing = *pTiming;
// BPM text
FOREACH_CONST( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, seg )
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -797,7 +889,7 @@ void NoteField::DrawPrimitives()
}
// Freeze text
FOREACH_CONST( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, seg )
FOREACH_CONST( StopSegment, timing.m_StopSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -808,18 +900,19 @@ void NoteField::DrawPrimitives()
}
// Warp text
FOREACH_CONST( WarpSegment, GAMESTATE->m_pCurSong->m_Timing.m_WarpSegments, seg )
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawWarpText( fBeat, seg->m_fEndBeat );
DrawWarpText( fBeat, seg->m_fLengthBeats );
}
}
// Time Signature text
FOREACH_CONST( TimeSignatureSegment, GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments, seg )
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -829,30 +922,36 @@ void NoteField::DrawPrimitives()
}
}
// Tickcount text
FOREACH_CONST( TickcountSegment, GAMESTATE->m_pCurSong->m_Timing.m_TickcountSegments, seg )
if( GAMESTATE->m_bIsUsingStepTiming )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
// Tickcount text
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, seg )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawTickcountText( fBeat, seg->m_iTicks );
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawTickcountText( fBeat, seg->m_iTicks );
}
}
}
// Combo text
FOREACH_CONST( ComboSegment, GAMESTATE->m_pCurSong->m_Timing.m_ComboSegments, seg )
if( GAMESTATE->m_bIsUsingStepTiming )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
// Combo text
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, seg )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawComboText( fBeat, seg->m_iCombo );
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawComboText( fBeat, seg->m_iCombo );
}
}
}
// Label text
FOREACH_CONST( LabelSegment, GAMESTATE->m_pCurSong->m_Timing.m_LabelSegments, seg )
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -861,6 +960,33 @@ void NoteField::DrawPrimitives()
DrawLabelText( fBeat, seg->m_sLabel );
}
}
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawSpeedText( fBeat, seg->m_fPercent, seg->m_fWait, seg->m_usMode );
}
}
}
// Speed text
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawFakeText( fBeat, seg->m_fLengthBeats );
}
}
}
// Course mods text
const Course *pCourse = GAMESTATE->m_pCurCourse;
@@ -872,7 +998,7 @@ void NoteField::DrawPrimitives()
FOREACH_CONST( Attack, ce.attacks, a )
{
float fSecond = a->fStartSecond;
float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond );
float fBeat = timing.GetBeatFromElapsedTime( fSecond );
if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
BeatToNoteRow(fBeat) <= iLastRowToDraw)
@@ -882,71 +1008,74 @@ void NoteField::DrawPrimitives()
}
}
}
// BGChange text
switch( GAMESTATE->m_EditMode )
if( !GAMESTATE->m_bIsUsingStepTiming )
{
case EditMode_Home:
case EditMode_CourseMods:
case EditMode_Practice:
break;
case EditMode_Full:
{
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
while( 1 )
// BGChange text
switch( GAMESTATE->m_EditMode )
{
case EditMode_Home:
case EditMode_CourseMods:
case EditMode_Practice:
break;
case EditMode_Full:
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
while( 1 )
{
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
}
}
if( viLowestIndex.empty() )
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
break;
}
if( IS_ON_SCREEN(fLowestBeat) )
{
vector<RString> vsBGChanges;
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
{
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
RString s = change.GetTextDescription();
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
vsBGChanges.push_back( s );
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
}
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
if( viLowestIndex.empty() )
{
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
break;
}
if( IS_ON_SCREEN(fLowestBeat) )
{
vector<RString> vsBGChanges;
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
{
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
RString s = change.GetTextDescription();
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
vsBGChanges.push_back( s );
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
}
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
}
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
}
}
break;
default:
ASSERT(0);
break;
default:
ASSERT(0);
}
}
// Draw marker bars
@@ -1012,8 +1141,8 @@ void NoteField::DrawPrimitives()
float fThrowAway;
bool bStartIsPastPeak = false;
bool bEndIsPastPeak = false;
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iEndRow), fThrowAway, bEndIsPastPeak );
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak );
bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels;
bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels;
@@ -1043,7 +1172,7 @@ void NoteField::DrawPrimitives()
displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GAMESTATE->m_fSongBeat;
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
}
}
@@ -1085,7 +1214,7 @@ void NoteField::DrawPrimitives()
continue; // skip
ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw,
iFirstRowToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) );
iFirstRowToDraw, GetDisplayedPosition(m_pPlayerState)->m_fSongBeat, GetDisplayedPosition(m_pPlayerState)->m_fMusicSeconds) );
// See if there is a hold step that begins on this index.
// Only do this if the noteskin cares.
@@ -1110,12 +1239,12 @@ void NoteField::DrawPrimitives()
bool bIsHopoPossible = (tn.bHopoPossible);
bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
displayCols->display[c].DrawTap( tn, c, NoteRowToBeat(q), bHoldNoteBeginsOnThisBeat,
displayCols->display[c].DrawTap( tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), bHoldNoteBeginsOnThisBeat,
bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(q) > GAMESTATE->m_fSongBeat;
bool bNoteIsUpcoming = NoteRowToBeat(q) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
}
@@ -1123,6 +1252,9 @@ void NoteField::DrawPrimitives()
}
cur->m_GhostArrowRow.Draw();
// restore the active player number
Actor::m_ActivePlayerNumber = pnLastActivePlayerNumber;
}
void NoteField::FadeToFail()
+6 -2
View File
@@ -1,6 +1,8 @@
#ifndef NOTE_FIELD_H
#define NOTE_FIELD_H
#include "TimingData.h"
#include "SongPosition.h"
#include "Sprite.h"
#include "ActorFrame.h"
#include "BitmapText.h"
@@ -56,16 +58,18 @@ protected:
void DrawMarkerBar( int fBeat );
void DrawAreaHighlight( int iStartBeat, int iEndBeat );
void DrawBPMText( const float fBeat, const float fBPM );
void DrawFreezeText( const float fBeat, const float fBPM, const float bDelay );
void DrawFreezeText( const float fBeat, const float fLength, const float bDelay );
void DrawWarpText( const float fBeat, const float fNewBeat );
void DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator );
void DrawTickcountText( const float fBeat, int iTicks );
void DrawComboText( const float fBeat, int iCombo );
void DrawLabelText( const float fBeat, RString sLabel );
void DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode );
void DrawFakeText( const float fBeat, const float fNewBeat );
void DrawAttackText( const float fBeat, const Attack &attack );
void DrawBGChangeText( const float fBeat, const RString sNewBGName );
float GetWidth() const;
const NoteData *m_pNoteData;
float m_fPercentFadeToFail; // -1 if not fading to fail
+6 -4
View File
@@ -16,7 +16,8 @@
#include <map>
#include "SpecialFiles.h"
NoteSkinManager* NOTESKIN = NULL; // global object accessable from anywhere in the program
/** @brief Have the NoteSkinManager available throughout the program. */
NoteSkinManager* NOTESKIN = NULL;
const RString GAME_COMMON_NOTESKIN_NAME = "common";
const RString GAME_BASE_NOTESKIN_NAME = "default";
@@ -199,7 +200,7 @@ bool NoteSkinManager::DoesNoteSkinExist( const RString &sSkinName )
vector<RString> asSkinNames;
GetAllNoteSkinNamesForGame( GAMESTATE->m_pCurGame, asSkinNames );
for( unsigned i=0; i<asSkinNames.size(); i++ )
if( 0==stricmp(sSkinName, asSkinNames[i]) )
if( sSkinName.EqualsNoCase(asSkinNames[i]) )
return true;
return false;
}
@@ -251,7 +252,7 @@ RString NoteSkinManager::GetMetric( const RString &sButtonName, const RString &s
int NoteSkinManager::GetMetricI( const RString &sButtonName, const RString &sValueName )
{
return atoi( GetMetric(sButtonName,sValueName) );
return StringToInt( GetMetric(sButtonName,sValueName) );
}
float NoteSkinManager::GetMetricF( const RString &sButtonName, const RString &sValueName )
@@ -261,7 +262,8 @@ float NoteSkinManager::GetMetricF( const RString &sButtonName, const RString &sV
bool NoteSkinManager::GetMetricB( const RString &sButtonName, const RString &sValueName )
{
return atoi( GetMetric(sButtonName,sValueName) ) != 0;
// Could also call GetMetricI here...hmm.
return StringToInt( GetMetric(sButtonName,sValueName) ) != 0;
}
apActorCommands NoteSkinManager::GetMetricA( const RString &sButtonName, const RString &sValueName )
-2
View File
@@ -43,11 +43,9 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set<RString> &Bl
SMLoader::GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMLoader::LoadFromDir( sPath, out );
#if defined(_MSC_VER) || defined(MACOSX)
SMALoader::GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMALoader::LoadFromDir( sPath, out );
#endif
DWILoader::GetApplicableFiles( sPath, list );
if( !list.empty() )
return DWILoader::LoadFromDir( sPath, out, BlacklistedImages );
+243 -186
View File
@@ -398,8 +398,8 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iMeasureNo = StringToInt( sName.substr(1, 3) );
int iBMSTrackNo = StringToInt( sName.substr(4, 2) );
RString nData = it->second;
int totalPairs = nData.size() / 2;
if( iBMSTrackNo != BMS_TRACK_TIME_SIG && iBMSTrackNo != 7 )
@@ -423,10 +423,10 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
// this is step or offset data. Looks like "#00705"
const RString &sData = it->second;
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iMeasureNo = StringToInt( sName.substr(1, 3) );
if( iMeasureNo < iStartMeasureNo )
continue;
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iBMSTrackNo = StringToInt( sName.substr(4, 2) );
if( iBMSTrackNo == BMS_TRACK_TIME_SIG )
out[iMeasureNo] = StringToFloat( sData );
}
@@ -435,9 +435,121 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out,
const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex )
static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
{
// Search for memoized file names:
{
RString nDataToSearchFor = nDataOriginal;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
return true;
}
}
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nDataOriginal) )
return false;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
RString nData = nDataOriginal;
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
{
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
return false;
}
// Let's again search for memoized file names (we got the normalized one!):
{
RString nDataToSearchFor = nData;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
}
// Now this is a new sample.
out.m_vsKeysoundFile.push_back( nData );
outKeysoundIndex = out.m_vsKeysoundFile.size() - 1;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
{
RString nDataToAdd = nData;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
static bool SearchForKeysound( const RString &sPath, RString sNoteId, const NameToData_t &mapNameToData, map<RString, int> &mapIdToKeysoundIndex, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
{
sNoteId.MakeLower();
{
map<RString, int>::iterator it = mapIdToKeysoundIndex.find(sNoteId);
if (it != mapIdToKeysoundIndex.end())
{
outKeysoundIndex = it->second;
return outKeysoundIndex >= 0;
}
}
RString sTagToLookFor = ssprintf( "#wav%s", sNoteId.c_str() );
RString nDataOriginal;
if( !GetTagFromMap( mapNameToData, sTagToLookFor, nDataOriginal ) )
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
return false;
}
bool retval = SearchForKeysound(sPath, nDataOriginal, mapFilenameToKeysoundIndex, out, outKeysoundIndex);
mapIdToKeysoundIndex[sNoteId] = outKeysoundIndex;
return retval;
}
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, Song &outSong, map<RString, int> &mapFilenameToKeysoundIndex )
{
map<RString, int> mapIdToKeysoundIndex;
MeasureToTimeSig_t sigAdjustments;
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
out.m_StepsType = StepsType_Invalid;
@@ -446,18 +558,128 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
int iPlayer = -1;
RString sData;
if( GetTagFromMap( mapNameToData, "#player", sData ) )
iPlayer = atoi(sData);
iPlayer = StringToInt(sData);
if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
out.SetMeter( atoi(sData) );
out.SetMeter( StringToInt(sData) );
NoteData ndNotes;
ndNotes.SetNumTracks( NUM_BMS_TRACKS );
// Read BPM
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.m_Timing.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
/* Read time signatures. Note that these can differ across files in the same
* song. */
MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( NameToData_t::const_iterator it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.m_Timing.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.m_Timing.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.m_Timing.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, outSong, sigAdjustments );
int iHoldStarts[NUM_BMS_TRACKS];
TapNote iHoldHeads[NUM_BMS_TRACKS];
@@ -475,8 +697,8 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
int iRawTrackNum = atoi( sName.substr(4,2).c_str() );
int iMeasureNo = StringToInt( sName.substr(1,3) );
int iRawTrackNum = StringToInt( sName.substr(4,2) );
int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
const RString &sNoteData = it->second;
@@ -487,10 +709,9 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
RString sNoteId = sNoteData.substr( i, 2 );
if( sNoteId != "00" )
{
vTapNotes.push_back( TAP_ORIGINAL_TAP );
map<RString,int>::const_iterator rInt = idToKeySoundIndex.find( sNoteId );
if( rInt != idToKeySoundIndex.end() )
vTapNotes.back().iKeysoundIndex = rInt->second;
TapNote tn = TAP_ORIGINAL_TAP;
SearchForKeysound( sPath, sNoteId, mapNameToData, mapIdToKeysoundIndex, mapFilenameToKeysoundIndex, outSong, tn.iKeysoundIndex );
vTapNotes.push_back( tn );
}
else
{
@@ -781,7 +1002,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
return true;
}
static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map<RString,int> &idToKeySoundIndexOut )
static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
{
RString sData;
if( GetTagFromMap(mapNameToData, "#title", sData) )
@@ -792,172 +1013,6 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( PREFSMAN->m_bQuirksMode )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
if( fBPM > 0.0f )
LOG->Trace( "Inserting new positive BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
else
LOG->Trace( "Inserting new negative BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
}
NameToData_t::const_iterator it;
for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName.Left(4) != "#wav" )
continue;
// this is keysound file name. Looks like "#WAV1A"
RString nData = it->second;
RString sWavID = sName.Right(2);
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nData) )
continue;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
sWavID.MakeUpper(); // HACK: undo the MakeLower()
out.m_vsKeysoundFile.push_back( nData );
idToKeySoundIndexOut[ sWavID ] = out.m_vsKeysoundFile.size()-1;
LOG->Trace( "Inserting keysound index %u '%s'", unsigned(out.m_vsKeysoundFile.size()-1), sWavID.c_str() );
}
// Time signature tags affect all other global timing tags, so read them first.
MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, out, sigAdjustmentsOut );
}
static void SlideDuplicateDifficulties( Song &p )
@@ -1031,7 +1086,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Create a Steps for each.
vector<Steps*> apSteps;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
apSteps.push_back( new Steps );
apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and
// figure out where each goes.
@@ -1100,9 +1155,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
if( apSteps[i]->GetDifficulty() == Difficulty_Medium )
iMainDataIndex = i;
MeasureToTimeSig_t sigAdjustments;
map<RString,int> idToKeysoundIndex;
ReadGlobalTags( aBMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex );
ReadGlobalTags( aBMSData[iMainDataIndex], out );
// The brackets before the difficulty are in common substring, so remove them if it's found.
if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' )
@@ -1124,16 +1177,20 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Now that we've parsed the keysound data, load the Steps from the rest
// of the .bms files.
map<RString, int> mapFilenameToKeysoundIndex;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
{
Steps* pNewNotes = apSteps[i];
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, sigAdjustments, idToKeysoundIndex );
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, out, mapFilenameToKeysoundIndex );
if( ok )
out.AddSteps( pNewNotes );
else
delete pNewNotes;
}
// set song's timing data to the main file.
out.m_SongTiming = apSteps[iMainDataIndex]->m_Timing;
SlideDuplicateDifficulties( out );
ConvertString( out.m_sMainTitle, "utf-8,japanese" );
+54 -26
View File
@@ -10,6 +10,7 @@
#include "GameInput.h"
#include "NotesLoader.h"
#include "PrefsManager.h"
#include "Difficulty.h"
#include <map>
@@ -140,6 +141,33 @@ static bool Is192( const RString &sStepData, size_t pos )
/** @brief All DWI files use 4 beats per measure. */
const int BEATS_PER_MEASURE = 4;
/* We prefer the normal names; recognize a number of others, too. (They'll get
* normalized when written to SMs, etc.) */
Difficulty DwiCompatibleStringToDifficulty( const RString& sDC )
{
RString s2 = sDC;
s2.MakeLower();
if( s2 == "beginner" ) return Difficulty_Beginner;
else if( s2 == "easy" ) return Difficulty_Easy;
else if( s2 == "basic" ) return Difficulty_Easy;
else if( s2 == "light" ) return Difficulty_Easy;
else if( s2 == "medium" ) return Difficulty_Medium;
else if( s2 == "another" ) return Difficulty_Medium;
else if( s2 == "trick" ) return Difficulty_Medium;
else if( s2 == "standard" ) return Difficulty_Medium;
else if( s2 == "difficult") return Difficulty_Medium;
else if( s2 == "hard" ) return Difficulty_Hard;
else if( s2 == "ssr" ) return Difficulty_Hard;
else if( s2 == "maniac" ) return Difficulty_Hard;
else if( s2 == "heavy" ) return Difficulty_Hard;
else if( s2 == "smaniac" ) return Difficulty_Challenge;
else if( s2 == "challenge" ) return Difficulty_Challenge;
else if( s2 == "expert" ) return Difficulty_Challenge;
else if( s2 == "oni" ) return Difficulty_Challenge;
else if( s2 == "edit" ) return Difficulty_Edit;
else return Difficulty_Invalid;
}
/**
* @brief Look through the notes tag to extract the data.
* @param sMode the steps type.
@@ -206,7 +234,7 @@ static bool LoadFromDWITokens(
DEFAULT_FAIL( out.m_StepsType );
}
int iNumFeet = atoi(sNumFeet);
int iNumFeet = StringToInt(sNumFeet);
// out.SetDescription(sDescription); // Don't put garbage in the description.
out.SetMeter(iNumFeet);
out.SetDifficulty( DwiCompatibleStringToDifficulty(sDescription) );
@@ -447,10 +475,10 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
}
// handle the data
if( 0==stricmp(sValueName,"FILE") )
if( sValueName.EqualsNoCase("FILE") )
out.m_sMusicFile = sParams[1];
else if( 0==stricmp(sValueName,"TITLE") )
else if( sValueName.EqualsNoCase("TITLE") )
{
NotesLoader::GetMainAndSubTitlesFromFullTitle( sParams[1], out.m_sMainTitle, out.m_sSubTitle );
@@ -460,38 +488,38 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
ConvertString( out.m_sSubTitle, "utf-8,english" );
}
else if( 0==stricmp(sValueName,"ARTIST") )
else if( sValueName.EqualsNoCase("ARTIST") )
{
out.m_sArtist = sParams[1];
ConvertString( out.m_sArtist, "utf-8,english" );
}
else if( 0==stricmp(sValueName,"GENRE") )
else if( sValueName.EqualsNoCase("GENRE") )
{
out.m_sGenre = sParams[1];
ConvertString( out.m_sGenre, "utf-8,english" );
}
else if( 0==stricmp(sValueName,"CDTITLE") )
else if( sValueName.EqualsNoCase("CDTITLE") )
out.m_sCDTitleFile = sParams[1];
else if( 0==stricmp(sValueName,"BPM") )
else if( sValueName.EqualsNoCase("BPM") )
{
const float fBPM = StringToFloat( sParams[1] );
if( PREFSMAN->m_bQuirksMode )
{
out.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
}
else{
if( fBPM > 0.0f )
out.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
else
LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
else if( 0==stricmp(sValueName,"DISPLAYBPM") )
else if( sValueName.EqualsNoCase("DISPLAYBPM") )
{
// #DISPLAYBPM:[xxx..xxx]|[xxx]|[*];
int iMin, iMax;
@@ -515,17 +543,17 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
}
}
else if( 0==stricmp(sValueName,"GAP") )
else if( sValueName.EqualsNoCase("GAP") )
// the units of GAP is 1/1000 second
out.m_Timing.m_fBeat0OffsetInSeconds = -atoi( sParams[1] ) / 1000.0f;
out.m_SongTiming.m_fBeat0OffsetInSeconds = -StringToInt( sParams[1] ) / 1000.0f;
else if( 0==stricmp(sValueName,"SAMPLESTART") )
else if( sValueName.EqualsNoCase("SAMPLESTART") )
out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]);
else if( 0==stricmp(sValueName,"SAMPLELENGTH") )
else if( sValueName.EqualsNoCase("SAMPLELENGTH") )
out.m_fMusicSampleLengthSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]);
else if( 0==stricmp(sValueName,"FREEZE") )
else if( sValueName.EqualsNoCase("FREEZE") )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
@@ -542,12 +570,12 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f );
float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f;
out.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
out.m_SongTiming.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds );
}
}
else if( 0==stricmp(sValueName,"CHANGEBPM") || 0==stricmp(sValueName,"BPMCHANGE") )
else if( sValueName.EqualsNoCase("CHANGEBPM") || sValueName.EqualsNoCase("BPMCHANGE") )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
@@ -567,7 +595,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( fBPM > 0.0f )
{
BPMSegment bs( iStartIndex, fBPM );
out.AddBPMSegment( bs );
out.m_SongTiming.AddBPMSegment( bs );
}
else
{
@@ -577,12 +605,12 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
}
}
else if( 0==stricmp(sValueName,"SINGLE") ||
0==stricmp(sValueName,"DOUBLE") ||
0==stricmp(sValueName,"COUPLE") ||
0==stricmp(sValueName,"SOLO") )
else if( sValueName.EqualsNoCase("SINGLE") ||
sValueName.EqualsNoCase("DOUBLE") ||
sValueName.EqualsNoCase("COUPLE") ||
sValueName.EqualsNoCase("SOLO") )
{
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
LoadFromDWITokens(
sParams[0],
sParams[1],
@@ -597,8 +625,8 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
else
delete pNewNotes;
}
else if( 0==stricmp(sValueName,"DISPLAYTITLE") ||
0==stricmp(sValueName,"DISPLAYARTIST") )
else if( sValueName.EqualsNoCase("DISPLAYTITLE") ||
sValueName.EqualsNoCase("DISPLAYARTIST") )
{
/* We don't want to support these tags. However, we don't want
* to pick up images used here as song images (eg. banners). */
@@ -628,7 +656,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
// do nothing. We don't care about this value name
}
}
out.TidyUpData();
return true;
}
+238
View File
@@ -0,0 +1,238 @@
#include "global.h"
#include "NotesLoaderJson.h"
#include "json/value.h"
#include "TimingData.h"
#include "RageUtil.h"
#include "JsonUtil.h"
#include "BackgroundUtil.h"
#include "NoteData.h"
#include "Song.h"
#include "Steps.h"
#include "GameManager.h"
void NotesLoaderJson::GetApplicableFiles( const RString &sPath, vector<RString> &out )
{
GetDirListing( sPath + RString("*.json"), out );
}
void Deserialize(BPMSegment &seg, const Json::Value &root)
{
seg.m_iStartRow = BeatToNoteRow((float)root["Beat"].asDouble());
seg.m_fBPS = (float)(root["BPM"].asDouble() / 60);
}
static void Deserialize(StopSegment &seg, const Json::Value &root)
{
seg.m_iStartRow = BeatToNoteRow((float)(root["Beat"].asDouble()));
seg.m_fStopSeconds = (float)root["Seconds"].asDouble();
}
static void Deserialize(TimingData &td, const Json::Value &root)
{
JsonUtil::DeserializeVectorObjects( td.m_BPMSegments, Deserialize, root["BpmSegments"] );
JsonUtil::DeserializeVectorObjects( td.m_StopSegments, Deserialize, root["StopSegments"] );
}
static void Deserialize(LyricSegment &o, const Json::Value &root)
{
o.m_fStartTime = (float)root["StartTime"].asDouble();
o.m_sLyric = root["Lyric"].asString();
o.m_Color.FromString( root["Color"].asString() );
}
static void Deserialize(BackgroundDef &o, const Json::Value &root)
{
o.m_sEffect = root["Effect"].asString();
o.m_sFile1 = root["File1"].asString();
o.m_sFile2 = root["File2"].asString();
o.m_sColor1 = root["Color1"].asString();
}
static void Deserialize(BackgroundChange &o, const Json::Value &root )
{
Deserialize( o.m_def, root["Def"] );
o.m_fStartBeat = (float)root["StartBeat"].asDouble();
o.m_fRate = (float)root["Rate"].asDouble();
o.m_sTransition = root["Transition"].asString();
}
static void Deserialize( TapNote &o, const Json::Value &root )
{
//if( o.type != TapNote::tap )
if( root.isInt() )
o.type = (TapNote::Type)root["Type"].asInt();
//if( o.type == TapNote::hold_head )
o.subType = (TapNote::SubType)root["SubType"].asInt();
//root["Source"] = (int)source;
//if( !o.sAttackModifiers.empty() )
o.sAttackModifiers = root["AttackModifiers"].asString();
//if( o.fAttackDurationSeconds > 0 )
o.fAttackDurationSeconds = (float)root["AttackDurationSeconds"].asDouble();
//if( o.bKeysound )
o.iKeysoundIndex = root["KeysoundIndex"].asInt();
//if( o.iDuration > 0 )
o.iDuration = root["Duration"].asInt();
//if( o.pn != PLAYER_INVALID )
o.pn = (PlayerNumber)root["PlayerNumber"].asInt();
}
static void Deserialize( StepsType st, NoteData &nd, const Json::Value &root )
{
int iTracks = nd.GetNumTracks();
nd.SetNumTracks( iTracks );
for( unsigned i=0; i<root.size(); i++ )
{
Json::Value root2 = root[i];
float fBeat = (float)root2[(unsigned)0].asDouble();
int iRow = BeatToNoteRow(fBeat);
int iTrack = root2[1].asInt();
const Json::Value &root3 = root2[2];
TapNote tn;
Deserialize( tn, root3 );
nd.SetTapNote( iTrack, iRow, tn );
}
}
static void Deserialize( RadarValues &o, const Json::Value &root )
{
FOREACH_ENUM( RadarCategory, rc )
{
o.m_Values.f[rc] = (float)root[ RadarCategoryToString(rc) ].asDouble();
}
}
static void Deserialize( Steps &o, const Json::Value &root )
{
o.m_StepsType = GAMEMAN->StringToStepsType(root["StepsType"].asString());
o.Decompress();
NoteData nd;
Deserialize( o.m_StepsType, nd, root["NoteData"] );
o.SetNoteData( nd );
//o.SetHash( root["Hash"].asInt() );
o.SetDescription( root["Description"].asString() );
o.SetDifficulty( StringToDifficulty(root["Difficulty"].asString()) );
o.SetMeter( root["Meter"].asInt() );
RadarValues rv[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
{
Deserialize( rv[pn], root["RadarValues"] );
}
o.SetCachedRadarValues( rv );
}
static void Deserialize( Song &out, const Json::Value &root )
{
out.SetSongDir( root["SongDir"].asString() );
out.m_sGroupName = root["GroupName"].asString();
out.m_sMainTitle = root["Title"].asString();
out.m_sSubTitle = root["SubTitle"].asString();
out.m_sArtist = root["Artist"].asString();
out.m_sMainTitleTranslit = root["TitleTranslit"].asString();
out.m_sSubTitleTranslit = root["SubTitleTranslit"].asString();
out.m_sGenre = root["Genre"].asString();
out.m_sCredit = root["Credit"].asString();
out.m_sBannerFile = root["Banner"].asString();
out.m_sBackgroundFile = root["Background"].asString();
out.m_sLyricsFile = root["LyricsFile"].asString();
out.m_sCDTitleFile = root["CDTitle"].asString();
out.m_sMusicFile = root["Music"].asString();
out.m_SongTiming.m_fBeat0OffsetInSeconds = (float)root["Offset"].asDouble();
out.m_fMusicSampleStartSeconds = (float)root["SampleStart"].asDouble();
out.m_fMusicSampleLengthSeconds = (float)root["SampleLength"].asDouble();
RString sSelectable = root["Selectable"].asString();
if( sSelectable.EqualsNoCase("YES") )
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else if( sSelectable.EqualsNoCase("NO") )
out.m_SelectionDisplay = out.SHOW_NEVER;
out.m_fFirstBeat = (float)root["FirstBeat"].asDouble();
out.m_fLastBeat = (float)root["LastBeat"].asDouble();
out.m_sSongFileName = root["SongFileName"].asString();
out.m_bHasMusic = root["HasMusic"].asBool();
out.m_bHasBanner = root["HasBanner"].asBool();
out.m_fMusicLengthSeconds = (float)root["MusicLengthSeconds"].asDouble();
RString sDisplayBPMType = root["DisplayBpmType"].asString();
if( sDisplayBPMType == "*" )
out.m_DisplayBPMType = DISPLAY_BPM_RANDOM;
else
out.m_DisplayBPMType = DISPLAY_BPM_SPECIFIED;
if( out.m_DisplayBPMType == DISPLAY_BPM_SPECIFIED )
{
out.m_fSpecifiedBPMMin = (float)root["SpecifiedBpmMin"].asDouble();
out.m_fSpecifiedBPMMax = (float)root["SpecifiedBpmMax"].asDouble();
}
Deserialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::DeserializeVectorObjects( out.m_LyricSegments, Deserialize, root["LyricSegments"] );
{
const Json::Value &root2 = root["BackgroundChanges"];
FOREACH_BackgroundLayer( bl )
{
const Json::Value &root3 = root2[bl];
vector<BackgroundChange> &vBgc = out.GetBackgroundChanges(bl);
JsonUtil::DeserializeVectorObjects( vBgc, Deserialize, root3 );
}
}
{
vector<BackgroundChange> &vBgc = out.GetForegroundChanges();
JsonUtil::DeserializeVectorObjects( vBgc, Deserialize, root["ForegroundChanges"] );
}
JsonUtil::DeserializeArrayValuesIntoVector( out.m_vsKeysoundFile, root["KeySounds"] );
{
vector<Steps*> vpSteps;
JsonUtil::DeserializeVectorPointers<Steps>( vpSteps, Deserialize, root["Charts"] );
FOREACH( Steps*, vpSteps, iter )
out.AddSteps( *iter );
}
}
bool NotesLoaderJson::LoadFromJsonFile( const RString &sPath, Song &out )
{
Json::Value root;
if( !JsonUtil::LoadFromFileShowErrors(root,sPath) )
return false;
Deserialize(out, root);
return true;
}
bool NotesLoaderJson::LoadFromDir( const RString &sPath, Song &out )
{
return LoadFromJsonFile(sPath, out);
}
/*
* (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.
*/
+41
View File
@@ -0,0 +1,41 @@
/* JsonLoader - Reads a Song from a .json file. */
#ifndef NotesLoaderJson_H
#define NotesLoaderJson_H
#include "NotesLoader.h"
class Song;
namespace NotesLoaderJson
{
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadFromDir( const RString &sPath, Song &out );
bool LoadFromJsonFile( const RString &sPath, Song &out );
};
#endif
/*
* (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.
*/
+196 -91
View File
@@ -9,6 +9,51 @@
#include "Song.h"
#include "Steps.h"
static void HandleBunki( TimingData &timing, const float fEarlyBPM,
const float fCurBPM, const float fGap,
const float fPos )
{
const float BeatsPerSecond = fEarlyBPM / 60.0f;
const float beat = (fPos + fGap) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f",
fEarlyBPM, BeatsPerSecond, fPos, beat );
timing.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) );
}
static bool HandlePipeChars( TimingData &timing, const RString sNoteRow,
const float fCurBeat, int &iTickCount )
{
RString temp = sNoteRow.substr(2,sNoteRow.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(sNoteRow, "|T"))
{
iTickCount = static_cast<int>(numTemp);
timing.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) );
return true;
}
else if (BeginsWith(sNoteRow, "|B"))
{
timing.SetBPMAtBeat( fCurBeat, numTemp );
return true;
}
else if (BeginsWith(sNoteRow, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / timing.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
fCurDelay += timing.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
else if (BeginsWith(sNoteRow, "|D"))
{
float fCurDelay = timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += numTemp / 1000;
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
return false;
}
static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, bool bKIUCompliant )
{
LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );
@@ -25,6 +70,9 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
// 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++ )
{
@@ -32,25 +80,100 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
RString sValueName = sParams[0];
sValueName.MakeUpper();
// handle the data
if( sValueName=="TICKCOUNT" )
/* handle the data...well, not this data: not related to steps.
* Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
if (sValueName=="TITLE" || EndsWith(sValueName, "INTRO")
|| EndsWith(sValueName, "FILE") )
{
iTickCount = atoi( sParams[1] );
;
}
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
stepsTiming.AddBPMSegment( 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.m_fBeat0OffsetInSeconds = 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.AddTickcountSegment(TickcountSegment(0, iTickCount));
}
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
else if( sValueName=="DIFFICULTY" )
{
out.SetMeter( max(atoi(sParams[1]), 0) );
out.SetMeter( max(StringToInt(sParams[1]), 0) );
}
// new cases from Aldo_MX's fork:
else if( sValueName=="PLAYER" )
@@ -60,13 +183,34 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
if( sPlayer.find( "double" ) != string::npos )
bDoublesChart = true;
}
// This should always be last.
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
}
if( iTickCount == -1 )
{
iTickCount = 2; // Direct Move 0.5 has a default value of 4... -aj
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
@@ -133,6 +277,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
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( ssprintf("%i", out.m_StepsType) );
}
@@ -146,6 +291,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
int newTick = -1;
float fCurBeat = 0.0f;
float prevBeat = 0.0f; // Used for hold tails.
for( unsigned r=0; r<vNoteRows.size(); r++ )
{
RString& sRowString = vNoteRows[r];
@@ -155,7 +301,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
continue; // skip
// All 2s indicates the end of the song.
if( sRowString == "2222222222222" )
else if( sRowString == "2222222222222" )
{
// Finish any holds that didn't get...well, finished.
for( t=0; t < notedata.GetNumTracks(); t++ )
@@ -171,29 +317,21 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
break;
}
// Why do this? Rows made with precise DM05 tags can go up to 13 too -DaisuMaster
//if( sRowString.size() != 13 )
//this is wrong in many ways...
/*if( bKIUCompliant )
else if( BeginsWith(sRowString, "|") )
{
LOG->UserLog( "Song file", sPath, "has illegal syntax \"%s\" which can't be in KIU complient files.",
sRowString.c_str() );
return false;
//In other words: you can't mix ksf's with DM05 tags and ksf's without any DM05 tags
//Either one set or another will be read...
}*/
if( BeginsWith(sRowString, "|B") || BeginsWith(sRowString, "|D") || BeginsWith(sRowString, "|E") )
{
// These don't have to be worried about here: the changes and stops were already added.
continue;
}
else if ( BeginsWith(sRowString, "|T") )
{
RString temp = sRowString.substr(2,sRowString.size()-3);
newTick = atoi(temp);
bTickChangeNeeded = true;
if (bKIUCompliant)
{
// Log an error, ignore the line.
continue;
}
if ( !HandlePipeChars( stepsTiming, sRowString, fCurBeat, iTickCount ) )
{
// LOG it first.
}
continue;
}
else
{
// Is this why improper ksf or some kiucompilant ksf mixed with dm05 ksf are ignored?? -DaisuMaster
@@ -270,6 +408,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
}
out.SetNoteData( notedata );
out.m_Timing = stepsTiming;
out.TidyUpData();
@@ -286,12 +425,12 @@ static void LoadTags( const RString &str, Song &out )
split( str, " - ", asBits, false );
// Ignore the difficulty, since we get that elsewhere.
if( asBits.size() == 3 && (
!stricmp(asBits[2], "double") ||
!stricmp(asBits[2], "easy") ||
!stricmp(asBits[2], "normal") ||
!stricmp(asBits[2], "hard") ||
!stricmp(asBits[2], "crazy") ||
!stricmp(asBits[2], "nightmare"))
asBits[2].EqualsNoCase("double") ||
asBits[2].EqualsNoCase("easy") ||
asBits[2].EqualsNoCase("normal") ||
asBits[2].EqualsNoCase("hard") ||
asBits[2].EqualsNoCase("crazy") ||
asBits[2].EqualsNoCase("nightmare"))
)
{
asBits.erase( asBits.begin()+2, asBits.begin()+3 );
@@ -358,7 +497,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
out.AddBPMSegment( BPMSegment(0, BPM1) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
@@ -383,7 +522,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="STARTTIME" )
{
SMGap1 = -StringToFloat( sParams[1] )/100;
out.m_Timing.m_fBeat0OffsetInSeconds = SMGap1;
out.m_SongTiming.m_fBeat0OffsetInSeconds = SMGap1;
}
// This is currently required for more accurate KIU BPM changes.
else if( sValueName=="STARTTIME2" )
@@ -401,7 +540,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
/* TICKCOUNT will be used below if there are DM compliant BPM changes
* and stops. It will be called again in LoadFromKSFFile for the
* actual steps. */
iTickCount = atoi( sParams[1] );
iTickCount = StringToInt( sParams[1] );
iTickCount = iTickCount > 0 ? iTickCount : 2; // again, Direct Move uses 4 as a default.
// add a tickcount for those using the [Player]
// CheckpointsUseTimeSignatures metric. -aj
@@ -409,7 +548,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(0.0f);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs );
out.m_SongTiming.AddTickcountSegment( tcs );
}
else if ( sValueName=="STEP" )
{
@@ -465,21 +604,12 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
{
if( BPM2 > 0 && BPMPos2 > 0 )
{
const float BeatsPerSecond = BPM1 / 60.0f;
const float beat = (BPMPos2 + SMGap1) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos2 %f, beat %f",
BPM1, BeatsPerSecond, BPMPos2, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM2) );
HandleBunki( out.m_SongTiming, BPM1, BPM2, SMGap1, BPMPos2 );
}
if( BPM3 > 0 && BPMPos3 > 0 )
{
const float BeatsPerSecond = BPM2 / 60.0f;
//The line below isn't perfect, but works better than previous versions.
const float beat = (BPMPos3 + SMGap2) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos3 %f, beat %f",
BPM2, BeatsPerSecond, BPMPos3, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM3) );
HandleBunki( out.m_SongTiming, BPM2, BPM3, SMGap2, BPMPos3 );
}
}
else
@@ -507,40 +637,11 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") )
{
bDMRequired = true;
RString temp = NoteRowString.substr(2,NoteRowString.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(NoteRowString, "|T"))
if ( !HandlePipeChars( out.m_SongTiming, NoteRowString, fCurBeat, iTickCount ) )
{
iTickCount = (int)numTemp;
TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(fCurBeat);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs );
continue;
}
else if (BeginsWith(NoteRowString, "|B"))
{
float fCurBpm = (float)numTemp;
//out.m_Timing.AddBPMSegment( BPMSegment( BeatToNoteRow(fCurBeat), (float)numTemp ) );
out.m_Timing.SetBPMAtBeat( fCurBeat, fCurBpm );
continue;
}
else if (BeginsWith(NoteRowString, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / out.m_Timing.GetBPMAtBeat(fCurBeat) * (float)numTemp / iTickCount;
fCurDelay += out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
}
else if (BeginsWith(NoteRowString, "|D"))
{
float fCurDelay = out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += (float)numTemp / 1000;
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
// LOG it first.
}
continue;
}
else
{
@@ -580,16 +681,19 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
ASSERT( arrayKSFFileNames.size() );
bool bKIUCompliant = false;
/* If only the first file is read, it will cause problems for other simfiles with
* different BPM changes and tickcounts. This command will probably have to be
* changed in the future. */
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[0], out, bKIUCompliant) )
/* 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. */
unsigned files = arrayKSFFileNames.size();
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
return false;
// load the Steps from the rest of the KSF files
for( unsigned i=0; i<arrayKSFFileNames.size(); i++ )
for( unsigned i=0; i<files; i++ )
{
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
if( !LoadFromKSFFile(out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
{
delete pNewNotes;
@@ -598,6 +702,7 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
out.AddSteps( pNewNotes );
}
out.TidyUpData();
return true;
}
+5 -4
View File
@@ -682,7 +682,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT);
bpmSeg.m_fBPS = float( 1. / fSecondsPerBeat );
songOut.m_Timing.AddBPMSegment( bpmSeg );
songOut.m_SongTiming.AddBPMSegment( bpmSeg );
}
FOREACH_CONST( MidiFileIn::TimeSignatureChange, midi.timeSignatureEvents_, iter )
@@ -692,7 +692,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
seg.m_iNumerator = iter->numerator;
seg.m_iDenominator = iter->denominator;
songOut.m_Timing.AddTimeSignatureSegment( seg );
songOut.m_SongTiming.AddTimeSignatureSegment( seg );
}
@@ -877,13 +877,13 @@ skip_track:
}
}
Steps *pSteps = new Steps;
Steps *pSteps = songOut.CreateSteps();
pSteps->m_StepsType = StepsType_guitar_five;
pSteps->SetDifficulty( (Difficulty)(gd+1) );
pSteps->SetNoteData( noteData );
songOut.AddSteps( pSteps );
}
return true;
}
@@ -956,6 +956,7 @@ bool MidiLoader::LoadFromDir( const RString &sDir, Song &out )
if( !LoadFromMidi(sDir+vsFiles[0], out) )
return false;
out.TidyUpData();
return true;
}
+16 -16
View File
@@ -346,8 +346,8 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
// this is step or offset data. Looks like "#00705"
const RString &sData = it->second;
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iPMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iMeasureNo = StringToInt( sName.substr(1, 3) );
int iPMSTrackNo = StringToInt( sName.substr(4, 2) );
if( iPMSTrackNo == PMS_TRACK_TIME_SIG )
out[iMeasureNo] = StringToFloat( sData );
}
@@ -367,9 +367,9 @@ static bool LoadFromPMSFile( const RString &sPath, const NameToData_t &mapNameTo
int iPlayer = -1;
RString sData;
if( GetTagFromMap( mapNameToData, "#player", sData ) )
iPlayer = atoi(sData);
iPlayer = StringToInt(sData);
if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
out.SetMeter( atoi(sData) );
out.SetMeter( StringToInt(sData) );
NoteData ndNotes;
ndNotes.SetNumTracks( NUM_PMS_TRACKS );
@@ -396,8 +396,8 @@ static bool LoadFromPMSFile( const RString &sPath, const NameToData_t &mapNameTo
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
int iRawTrackNum = atoi( sName.substr(4,2).c_str() );
int iMeasureNo = StringToInt( sName.substr(1,3) );
int iRawTrackNum = StringToInt( sName.substr(4,2) );
int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
const RString &sNoteData = it->second;
@@ -591,7 +591,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
@@ -649,8 +649,8 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iPMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iMeasureNo = StringToInt( sName.substr(1, 3) );
int iPMSTrackNo = StringToInt( sName.substr(4, 2) );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
@@ -673,7 +673,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
case PMS_TRACK_BPM:
if( iVal > 0 )
{
out.SetBPMAtBeat( fBeat, (float) iVal );
out.m_SongTiming.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
@@ -694,7 +694,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( BeatToNoteRow(fBeat), fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() );
}
else
@@ -716,12 +716,12 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBPS = out.m_SongTiming.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg );
out.m_SongTiming.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
@@ -750,7 +750,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( iStepIndex, fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartRow), newSeg.GetBPM() );
}
@@ -843,7 +843,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
/* Create a Steps for each. */
vector<Steps*> apSteps;
for( unsigned i=0; i<arrayPMSFileNames.size(); i++ )
apSteps.push_back( new Steps );
apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and
// figure out where each goes.
@@ -940,7 +940,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
ConvertString( out.m_sArtist, "utf-8,japanese" );
ConvertString( out.m_sGenre, "utf-8,japanese" );
out.TidyUpData();
return true;
}
+369 -330
View File
@@ -15,11 +15,6 @@
/** @brief The maximum file size for edits. */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
void SMLoader::LoadFromSMTokens(
RString sStepsType,
@@ -45,7 +40,7 @@ void SMLoader::LoadFromSMTokens(
out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType );
out.SetDescription( sDescription );
out.SetCredit( sDescription ); // this is often used for both.
out.SetDifficulty( DwiCompatibleStringToDifficulty(sDifficulty) );
out.SetDifficulty( StringToDifficulty(sDifficulty) );
// Handle hacks that originated back when StepMania didn't have
// Difficulty_Challenge. (At least v1.64, possibly v3.0 final...)
@@ -60,7 +55,7 @@ void SMLoader::LoadFromSMTokens(
out.SetDifficulty( Difficulty_Challenge );
}
out.SetMeter( atoi(sMeter) );
out.SetMeter( StringToInt(sMeter) );
vector<RString> saValues;
split( sRadarValues, ",", saValues, true );
int categories = NUM_RadarCategory - 1; // Fakes aren't counted in the radar values.
@@ -103,6 +98,331 @@ bool SMLoader::LoadTimingFromFile( const RString &fn, TimingData &out )
return true;
}
void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RString &sPath, const RString &sParam )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParam, ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
}
void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
{
// 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<RString> 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 );
}
}
}
void SMLoader::ProcessInstrumentTracks( Song &out, const RString &sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
}
bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
arrayBPMChangeExpressions[b].c_str() );
continue;
}
bNotEmpty = true;
const float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat - negBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat - highspeedBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
return bNotEmpty;
}
void SMLoader::ProcessStops( TimingData &out, const RString sParam )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
}
}
void SMLoader::ProcessDelays( TimingData &out, const RString sParam )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayDelayValues[0] );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
void SMLoader::ProcessTimeSignatures( 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() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), StringToInt( 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 );
continue;
}
if( seg.m_iDenominator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
void SMLoader::ProcessTickcounts( TimingData &out, const RString sParam )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
}
}
void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
{
out.m_fBeat0OffsetInSeconds = 0;
@@ -123,255 +443,27 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
}
else if( sValueName=="BPMS" )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
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() );
continue;
}
const float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg;
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
ProcessBPMs(out, sParams[1]);
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// 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() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
}
ProcessStops(out, sParams[1]);
}
else if( sValueName=="DELAYS" )
{
vector<RString> arrayDelayExpressions;
split( sParams[1], ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// 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(), arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayDelayValues[0] );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
ProcessDelays(out, sParams[1]);
}
else if( sValueName=="TIMESIGNATURES" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg;
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 );
continue;
}
if( seg.m_iDenominator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
ProcessTimeSignatures(out, sParams[1]);
}
else if( sValueName=="TICKCOUNTS" )
{
vector<RString> arrayTickcountExpressions;
split( sParams[1], ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// 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(), arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
int iTicks = atoi( arrayTickcountValues[1] );
// you're lazy, let SM do the work for you... -DaisuMaster
if( iTicks < 1) iTicks = 1;
if( iTicks > ROWS_PER_BEAT ) iTicks = ROWS_PER_BEAT;
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants
{
// LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks );
//out.AddTickcountSegment( new_seg );
}
else
{
//LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks );
//LOG->UserLog( "Song file", "(UNKNOWN)", "Clamping tickcount value to %d at beat %f.", iTicks, fTickcountBeat);
//etc
}
}
ProcessTickcounts(out, sParams[1]);
}
// Ensure all of the warps are handled right.
sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
@@ -401,8 +493,17 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString
change.m_sTransition = aBGChangeValues[8];
// fall through
case 8:
{
RString tmp = aBGChangeValues[7];
tmp.MakeLower();
if( ( tmp.find(".ini") != string::npos || tmp.find(".xml") != string::npos )
&& !PREFSMAN->m_bQuirksMode )
{
return false;
}
change.m_def.m_sFile2 = aBGChangeValues[7];
// fall through
}
case 7:
change.m_def.m_sEffect = aBGChangeValues[6];
// fall through
@@ -411,7 +512,7 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString
// Backward compatibility:
if( change.m_def.m_sEffect.empty() )
{
bool bLoop = atoi( aBGChangeValues[5] ) != 0;
bool bLoop = StringToInt( aBGChangeValues[5] ) != 0;
if( !bLoop )
change.m_def.m_sEffect = SBE_StretchNoLoop;
}
@@ -421,7 +522,7 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString
// Backward compatibility:
if( change.m_def.m_sEffect.empty() )
{
bool bRewindMovie = atoi( aBGChangeValues[4] ) != 0;
bool bRewindMovie = StringToInt( aBGChangeValues[4] ) != 0;
if( bRewindMovie )
change.m_def.m_sEffect = SBE_StretchRewind;
}
@@ -430,14 +531,23 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString
// param 9 overrides this.
// Backward compatibility:
if( change.m_sTransition.empty() )
change.m_sTransition = (atoi( aBGChangeValues[3] ) != 0) ? "CrossFade" : "";
change.m_sTransition = (StringToInt( aBGChangeValues[3] ) != 0) ? "CrossFade" : "";
// fall through
case 3:
change.m_fRate = StringToFloat( aBGChangeValues[2] );
// fall through
case 2:
{
RString tmp = aBGChangeValues[1];
tmp.MakeLower();
if( ( tmp.find(".ini") != string::npos || tmp.find(".xml") != string::npos )
&& !PREFSMAN->m_bQuirksMode )
{
return false;
}
change.m_def.m_sFile1 = aBGChangeValues[1];
// fall through
}
case 1:
change.m_fStartBeat = StringToFloat( aBGChangeValues[0] );
// fall through
@@ -457,8 +567,8 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
return false;
}
out.m_Timing.m_sFile = sPath;
LoadTimingFromSMFile( msd, out.m_Timing );
out.m_SongTiming.m_sFile = sPath;
LoadTimingFromSMFile( msd, out.m_SongTiming );
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -512,19 +622,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName=="INSTRUMENTTRACK" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
ProcessInstrumentTracks( out, sParams[1] );
}
else if( sValueName=="MUSICLENGTH" )
@@ -560,12 +658,12 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName=="HASMUSIC" )
{
if( bFromCache )
out.m_bHasMusic = atoi( sParams[1] ) != 0;
out.m_bHasMusic = StringToInt( sParams[1] ) != 0;
}
else if( sValueName=="HASBANNER" )
{
if( bFromCache )
out.m_bHasBanner = atoi( sParams[1] ) != 0;
out.m_bHasBanner = StringToInt( sParams[1] ) != 0;
}
else if( sValueName=="SAMPLESTART" )
@@ -596,20 +694,20 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName=="SELECTABLE" )
{
if(!stricmp(sParams[1],"YES"))
if(sParams[1].EqualsNoCase("YES"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else if(!stricmp(sParams[1],"NO"))
else if(sParams[1].EqualsNoCase("NO"))
out.m_SelectionDisplay = out.SHOW_NEVER;
// ROULETTE from 3.9. It was removed since UnlockManager can serve
// the same purpose somehow. This, of course, assumes you're using
// unlocks. -aj
else if(!stricmp(sParams[1],"ROULETTE"))
else if(sParams[1].EqualsNoCase("ROULETTE"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
/* The following two cases are just fixes to make sure simfiles that
* used 3.9+ features are not excluded here */
else if(!stricmp(sParams[1],"ES") || !stricmp(sParams[1],"OMES"))
else if(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else if( atoi(sParams[1]) > 0 )
else if( StringToInt(sParams[1]) > 0 )
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else
LOG->UserLog( "Song file", sPath, "has an unknown #SELECTABLE value, \"%s\"; ignored.", sParams[1].c_str() );
@@ -617,27 +715,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
ProcessBGChanges( out, sValueName, sPath, sParams[1]);
}
else if( sValueName=="FGCHANGES" )
@@ -661,47 +739,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
// 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<RString> 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 );
}
}
ProcessAttacks( out, sParams );
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -712,7 +750,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
continue;
}
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
LoadFromSMTokens(
sParams[1],
sParams[2],
@@ -733,7 +771,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
}
TidyUpData( out, bFromCache );
return true;
}
@@ -846,7 +884,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
if( !bAddStepsToSong )
return true;
Steps* pNewNotes = new Steps;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
@@ -926,6 +964,7 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache )
bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) );
} while(0);
}
song.TidyUpData();
}
/*
+19 -1
View File
@@ -3,11 +3,18 @@
#include "GameConstantsAndTypes.h"
#include "BackgroundUtil.h"
#include "MsdFile.h" // we require the struct from here.
class MsdFile;
class Song;
class Steps;
class TimingData;
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
/** @brief Reads a Song from an .SM file. */
namespace SMLoader
{
@@ -25,6 +32,17 @@ namespace SMLoader
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
bool ProcessBPMs( TimingData &, const RString );
void ProcessStops( TimingData &, const RString );
void ProcessDelays( TimingData &, const RString );
void ProcessTimeSignatures( TimingData &, const RString );
void ProcessTickcounts( TimingData &, const RString );
void ProcessBGChanges( Song &out, const RString &sValueName,
const RString &sPath, const RString &sParam );
void ProcessAttacks( Song &out, MsdFile::value_t sParams );
void ProcessInstrumentTracks( Song &out, const RString &sParam );
}
#endif
+489 -394
View File
@@ -12,12 +12,379 @@
#include "Song.h"
#include "SongManager.h"
#include "Steps.h"
#include "Attack.h"
/**
* @brief A custom .edit file can only be so big before we have to reject it.
*/
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. Only one SMA file is allowed per song." );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
}
float SMALoader::RowToBeat( RString sLine, const int iRowsPerBeat )
{
if( sLine.find("R") || sLine.find("r") )
{
sLine = sLine.Left(sLine.size()-1);
return StringToFloat( sLine ) / iRowsPerBeat;
}
else
{
return StringToFloat( sLine );
}
}
bool SMALoader::ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
arrayBPMChangeExpressions[b].c_str() );
continue;
}
bNotEmpty = true;
const float fBeat = RowToBeat( arrayBPMChangeValues[0], iRowsPerBeat );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat - negBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat - highspeedBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
return bNotEmpty;
}
void SMALoader::ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayFreezeValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
}
}
void SMALoader::ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayDelayValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
void SMALoader::ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = RowToBeat( arrayTickcountValues[0], iRowsPerBeat );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
}
}
void SMALoader::ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayMultiplierExpressions;
split( sParam, ",", arrayMultiplierExpressions );
for( unsigned f=0; f<arrayMultiplierExpressions.size(); f++ )
{
vector<RString> arrayMultiplierValues;
split( arrayMultiplierExpressions[f], "=", arrayMultiplierValues );
if( arrayMultiplierValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #MULTIPLIER value \"%s\" (must have exactly one '='), ignored.",
arrayMultiplierExpressions[f].c_str() );
continue;
}
const float fComboBeat = RowToBeat( arrayMultiplierValues[0], iRowsPerBeat );
const int iCombos = StringToInt( arrayMultiplierValues[1] );
ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos );
out.AddComboSegment( new_seg );
}
}
void SMALoader::ProcessBeatsPerMeasure( 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", "(UNKNOWN)", "has an invalid beats per measure change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), 4 );
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 );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
float BeatToSeconds(float fromBeat, RString toSomething)
{
return 0;
}
void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( RowToBeat(vs2[0], iRowsPerBeat) == 0 && vs2.size() == 2 ) // First one always seems to have 2.
{
vs2.push_back("0");
}
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = RowToBeat( vs2[0], iRowsPerBeat );
unsigned short tmp = ( (vs2[2].find("s") || vs2[2].find("S") )
? 1 : 0);
SpeedSegment seg( fBeat, StringToFloat( vs2[1] ), StringToFloat( vs2[2] ), tmp);
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f.", fBeat );
continue;
}
if( seg.m_fWait < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f, fWait %f.", fBeat, seg.m_fWait );
continue;
}
out.AddSpeedSegment( seg );
}
}
void SMALoader::ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFakeExpressions;
split( sParam, ",", arrayFakeExpressions );
for( unsigned b=0; b<arrayFakeExpressions.size(); b++ )
{
vector<RString> arrayFakeValues;
split( arrayFakeExpressions[b], "=", arrayFakeValues );
// XXX: Hard to tell which file caused this.
if( arrayFakeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #FAKES value \"%s\" (must have exactly one '='), ignored.",
arrayFakeExpressions[b].c_str() );
continue;
}
const float fBeat = RowToBeat( arrayFakeValues[0], iRowsPerBeat );
const float fNewBeat = StringToFloat( arrayFakeValues[1] );
if(fNewBeat > 0)
out.AddFakeSegment( FakeSegment(fBeat, fNewBeat) );
else
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Fake at beat %f, BPM %f.", fBeat, fNewBeat );
}
}
}
void SMALoader::LoadFromSMATokens(
RString sStepsType,
RString sDescription,
@@ -28,73 +395,9 @@ void SMALoader::LoadFromSMATokens(
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::LoadFromSMTokens()" );
// insert stepstype hacks from GameManager.cpp here? -aj
out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType );
out.SetDescription( sDescription );
out.SetCredit( sDescription ); // this is often used for both.
out.SetDifficulty( DwiCompatibleStringToDifficulty(sDifficulty) );
sDescription.MakeLower();
// 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 == "smaniac" )
out.SetDifficulty( Difficulty_Challenge );
// HACK: CHALLENGE used to be Difficulty_Hard with a special description.
if( sDescription == "challenge" )
out.SetDifficulty( Difficulty_Challenge );
}
out.SetMeter( atoi(sMeter) );
vector<RString> saValues;
split( sRadarValues, ",", saValues, true );
int categories = NUM_RadarCategory - 1; // Fakes aren't counted in the radar values.
if( saValues.size() == (unsigned)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] );
}
}
out.SetCachedRadarValues( v );
}
out.SetSMNoteData( sNoteData );
out.TidyUpData();
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. There can be only one!" );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
SMLoader::LoadFromSMTokens( sStepsType, sDescription,
sDifficulty, sMeter, sRadarValues,
sNoteData, out );
}
void SMALoader::TidyUpData( Song &song, bool bFromCache )
@@ -113,8 +416,12 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
return false;
}
out.m_Timing.m_sFile = sPath;
LoadTimingFromSMAFile( msd, out.m_Timing );
out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
int state = SMA_GETTING_SONG_INFO;
Steps* pNewNotes = NULL;
TimingData stepsTiming;
int iRowsPerBeat = -1; // Start with an invalid value: needed for checking.
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -168,19 +475,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName=="INSTRUMENTTRACK" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
SMLoader::ProcessInstrumentTracks( out, sParams[1] );
}
else if( sValueName=="MUSICLENGTH" )
@@ -202,7 +497,8 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
else if( sValueName=="LASTBEAT" )
{
; }
;
}
else if( sValueName=="SONGFILENAME" )
{
;
@@ -226,6 +522,11 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
//else if( sValueName=="SAMPLEPATH" )
//out.m_sMusicSamplePath = sParams[1];
else if( sValueName=="LISTSORT" )
{
;
}
else if( sValueName=="DISPLAYBPM" )
{
// #DISPLAYBPM:[xxx][xxx:xxx]|[*];
@@ -242,22 +543,57 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
}
else if( sValueName=="SMAVERSION" )
{
; // ignore it.
}
else if( sValueName=="ROWSPERBEAT" )
{
/* This value is used to help translate the timings
* the SMA format uses. Starting with the second
* appearance, it delimits NoteData. Right now, this
* value doesn't seem to be editable in SMA. When it
* becomes so, make adjustments to this code. */
if( iRowsPerBeat < 0 )
{
vector<RString> arrayBeatChangeExpressions;
split( sParams[1], ",", arrayBeatChangeExpressions );
vector<RString> arrayBeatChangeValues;
split( arrayBeatChangeExpressions[0], "=", arrayBeatChangeValues );
iRowsPerBeat = StringToInt(arrayBeatChangeValues[1]);
}
else
{
state = SMA_GETTING_STEP_INFO;
pNewNotes = new Steps;
}
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBeatsPerMeasure( timing, sParams[1] );
}
else if( sValueName=="SELECTABLE" )
{
if(!stricmp(sParams[1],"YES"))
if(sParams[1].EqualsNoCase("YES"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else if(!stricmp(sParams[1],"NO"))
else if(sParams[1].EqualsNoCase("NO"))
out.m_SelectionDisplay = out.SHOW_NEVER;
// ROULETTE from 3.9. It was removed since UnlockManager can serve
// the same purpose somehow. This, of course, assumes you're using
// unlocks. -aj
else if(!stricmp(sParams[1],"ROULETTE"))
else if(sParams[1].EqualsNoCase("ROULETTE"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
/* The following two cases are just fixes to make sure simfiles that
* used 3.9+ features are not excluded here */
else if(!stricmp(sParams[1],"ES") || !stricmp(sParams[1],"OMES"))
else if(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES"))
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else if( atoi(sParams[1]) > 0 )
else if( StringToInt(sParams[1]) > 0 )
out.m_SelectionDisplay = out.SHOW_ALWAYS;
else
LOG->UserLog( "Song file", sPath, "has an unknown #SELECTABLE value, \"%s\"; ignored.", sParams[1].c_str() );
@@ -265,27 +601,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
SMLoader::ProcessBGChanges( out, sValueName, sPath, sParams[1]);
}
else if( sValueName=="FGCHANGES" )
@@ -301,6 +617,65 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
}
else if( sValueName=="OFFSET" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
timing.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="BPMS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBPMs( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessStops( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="DELAYS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessDelays( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="TICKCOUNT" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessTickcounts( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="SPEED" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessSpeeds( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="MULTIPLIER" )
{
ProcessMultipliers( pNewNotes->m_Timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="FAKES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessFakes( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="METERTYPE" )
{
; // We don't use this...yet.
}
else if( sValueName=="KEYSOUNDS" )
{
split( sParams[1], ",", out.m_vsKeysoundFile );
@@ -309,47 +684,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
// 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<RString> 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 );
}
}
SMLoader::ProcessAttacks( out, sParams );
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -360,7 +695,6 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
continue;
}
Steps* pNewNotes = new Steps;
LoadFromSMATokens(
sParams[1],
sParams[2],
@@ -372,16 +706,13 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
out.AddSteps( pNewNotes );
}
/*
* We used to check for timing data in this section. That has
* since been moved to a dedicated function.
*/
else if( sValueName=="OFFSET" || sValueName=="BPMS" || sValueName=="STOPS" || sValueName=="FREEZES" || sValueName=="DELAYS" || sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" || sValueName=="TICKCOUNTS" )
else if( sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" )
;
else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
}
TidyUpData(out, false);
out.TidyUpData();
return true;
}
@@ -390,236 +721,6 @@ void SMALoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.sma"), out );
}
bool SMALoader::LoadTimingFromFile( const RString &fn, TimingData &out )
{
MsdFile msd;
if( !msd.ReadFile( fn, true ) ) // unescape
{
LOG->UserLog( "Song file", fn, "couldn't be loaded: %s", msd.GetError().c_str() );
return false;
}
out.m_sFile = fn;
LoadTimingFromSMAFile( msd, out );
return true;
}
void SMALoader::LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out )
{
out.m_fBeat0OffsetInSeconds = 0;
out.m_BPMSegments.clear();
out.m_StopSegments.clear();
out.m_WarpSegments.clear();
out.m_vTimeSignatureSegments.clear();
vector<WarpSegment> arrayWarpsFromNegativeBPMs;
//vector<WarpSegment> arrayWarpsFromNegativeStops;
int rowsPerMeasure = 0;
bool encountered = false;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
if( sValueName=="ROWSPERBEAT")
{
if( encountered )
{
break;
}
encountered = true;
rowsPerMeasure = atoi( sParams[1] );
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimeSignatureSegment new_seg;
new_seg.m_iStartRow = 0;
new_seg.m_iNumerator = atoi( sParams[1] );
new_seg.m_iDenominator = 4;
out.AddTimeSignatureSegment( new_seg );
}
else if( sValueName=="OFFSET" )
{
out.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="STOPS" )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// 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() );
continue;
}
float fFreezeBeat = 0;
RString beat = arrayFreezeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fFreezeBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fFreezeBeat = StringToFloat(beat);
}
//float fFreezeBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
if(fFreezeSeconds > 0.0f)
{
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
{
// negative stops (hi JS!) -aj
if( PREFSMAN->m_bQuirksMode )
{
// LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
}
else if( sValueName=="BPMS" )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
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() );
continue;
}
float fBeat = 0;
RString beat = arrayBPMChangeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fBeat = StringToFloat(beat);
}
//float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
// XXX: Remove Negatives Bug?
BPMSegment new_seg;
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
// convert negative BPMs into Warp segments
if( fNewBPM < 0.0f )
{
vector<RString> arrayNextBPMChangeValues;
// get next bpm in sequence
if((b+1) < arrayBPMChangeExpressions.size())
{
split( arrayBPMChangeExpressions[b+1], "=", arrayNextBPMChangeValues );
const float fNextPositiveBeat = StringToFloat( arrayNextBPMChangeValues[0] );
const float fNextPositiveBPM = StringToFloat( arrayNextBPMChangeValues[1] );
// tJumpPos = (tPosBPS-abs(negBPS)) + (gPosBPMPosition - fNegPosition)
float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
//float fWarpLengthBeats = fNextPositiveBeat + fDeltaBeat;
WarpSegment wsTemp(BeatToNoteRow(fBeat),fDeltaBeat);
arrayWarpsFromNegativeBPMs.push_back(wsTemp);
/*
LOG->Trace( ssprintf("==NotesLoSM negbpm==\nfnextposbeat = %f, fnextposbpm = %f,\nfdelta = %f, fwarpto = %f",
fNextPositiveBeat,
fNextPositiveBPM,
fDeltaBeat,
fWarpToBeat
) );
*/
/*
LOG->Trace( ssprintf("==Negative/Subtractive BPM in NotesLoader==\nNegBPM has noterow = %i, BPM = %f\nNextBPM @ noterow %i\nDelta value = %i noterows\nThis warp will have us end up at noterow %i",
BeatToNoteRow(fBeat), fNewBPM,
BeatToNoteRow(fNextPositiveBeat),
BeatToNoteRow(fDeltaBeat),
BeatToNoteRow(fWarpToBeat))
);
*/
//float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
/*
LOG->Trace( ssprintf("==NotesLoader Delta as NoteRows==\nfDeltaBeat = %f (beat)\nfDeltaBeat = (NextBPMSeg %f - abs(fBPS %f)) + (nextStartRow %i - thisRow %i)",
fDeltaBeat,(fNextPositiveBPM/60.0f),abs(fNewBPM/60.0f),BeatToNoteRow(fNextPositiveBeat),BeatToNoteRow(fBeat))
);
*/
out.AddBPMSegment( new_seg );
continue;
}
else
{
// last BPM is a negative one? ugh. -aj (MAX_NOTE_ROW exists btw)
out.AddBPMSegment( new_seg );
}
}
if(fNewBPM > 0.0f)
out.AddBPMSegment( new_seg );
else
{
out.m_bHasNegativeBpms = true;
// only add Negative BPMs in quirks mode -aj
if( PREFSMAN->m_bQuirksMode )
out.AddBPMSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM );
}
}
}
// Note: Even though it is possible to have Negative BPMs and Stops in
// a song along with Warps, we should not support files that contain
// both styles of warp tricks (Negatives vs. #WARPS).
// If Warps have been populated from Negative BPMs, then go through that
// instead of using the data in the Warps tag. This should be above,
// but it breaks compiling so...
if(arrayWarpsFromNegativeBPMs.size() > 0)
{
// zomg we already have some warps...
for( unsigned j=0; j<arrayWarpsFromNegativeBPMs.size(); j++ )
{
out.AddWarpSegment( arrayWarpsFromNegativeBPMs[j] );
}
}
// warp sorting will need to take place.
//sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
}
}
bool SMALoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
LOG->Trace( "SMALoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
@@ -702,7 +803,7 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
if( !bAddStepsToSong )
return true;
Steps* pNewNotes = new Steps;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMATokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
@@ -730,12 +831,6 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
return true;
}
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
/**
* @file
* @author Aldo Fregoso, Jason Felds (c) 2009-2011
+22 -2
View File
@@ -9,6 +9,16 @@ class Song;
class Steps;
class TimingData;
/**
* @brief The various states while parsing a .sma file.
*/
enum SMALoadingStates
{
SMA_GETTING_SONG_INFO, /**< Retrieving song information. */
SMA_GETTING_STEP_INFO, /**< Retrieving step information. */
NUM_SMALoadingStates /**< The number of states used. */
};
/** @brief Reads a Song from a .SMA file. */
namespace SMALoader
{
@@ -25,12 +35,22 @@ namespace SMALoader
bool LoadFromSMAFile( const RString &sPath, Song &out );
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadTimingFromFile( const RString &fn, TimingData &out );
void LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out );
bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
void ProcessBeatsPerMeasure( TimingData &out, const RString sParam );
bool ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam );
float RowToBeat( RString sLine, const int iRowsPerBeat );
};
#endif
+282 -546
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -16,8 +16,6 @@ enum SSCLoadingStates
{
GETTING_SONG_INFO, /**< Retrieving song information. */
GETTING_STEP_INFO, /**< Retrieving step information. */
GETTING_STEP_TIMING_INFO, /**< Retrieving a step's individual timing information. */
GETTING_NOTE_INFO, /**< Retrieving the specific notes. This state may be deprecated. */
NUM_SSCLoadingStates /**< The number of states used. */
};
@@ -25,6 +23,8 @@ enum SSCLoadingStates
const float VERSION_RADAR_FAKE = 0.53f;
/** @brief The version where WarpSegments started to be utilized. */
const float VERSION_WARP_SEGMENT = 0.56f;
/** @brief The version that formally introduced Split Timing. */
const float VERSION_SPLIT_TIMING = 0.7f;
/**
* @brief The SSCLoader handles all of the parsing needed for .ssc files.
@@ -75,6 +75,13 @@ namespace SSCLoader
* @param bFromCache a flag to determine if this song is loaded from a cache file.
*/
void TidyUpData( Song &song, bool bFromCache );
void ProcessWarps( TimingData &, const RString, const float );
void ProcessLabels( TimingData &, const RString );
void ProcessCombos( TimingData &, const RString );
void ProcessSpeeds( TimingData &, const RString );
void ProcessFakes( TimingData &, const RString );
}
#endif
/**
+11 -11
View File
@@ -350,10 +350,10 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
/* Write transliterations, if we have them, since DWI doesn't support UTF-8. */
f.PutLine( ssprintf("#TITLE:%s;", DwiEscape(out.GetTranslitFullTitle()).c_str()) );
f.PutLine( ssprintf("#ARTIST:%s;", DwiEscape(out.GetTranslitArtist()).c_str()) );
ASSERT( out.m_Timing.m_BPMSegments[0].m_iStartRow == 0 );
ASSERT( out.m_SongTiming.m_BPMSegments[0].m_iStartRow == 0 );
f.PutLine( ssprintf("#FILE:%s;", DwiEscape(out.m_sMusicFile).c_str()) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_Timing.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_Timing.m_fBeat0OffsetInSeconds*1000 )) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_SongTiming.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_SongTiming.m_fBeat0OffsetInSeconds*1000 )) );
f.PutLine( ssprintf("#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds) );
f.PutLine( ssprintf("#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds) );
if( out.m_sCDTitleFile.size() )
@@ -374,29 +374,29 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
break;
}
if( !out.m_Timing.m_StopSegments.empty() )
if( !out.m_SongTiming.m_StopSegments.empty() )
{
f.Write( "#FREEZE:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
f.Write( ssprintf("%.3f=%.3f", fs.m_iStartRow * 4.0f / ROWS_PER_BEAT,
roundf(fs.m_fStopSeconds*1000)) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
}
if( out.m_Timing.m_BPMSegments.size() > 1)
if( out.m_SongTiming.m_BPMSegments.size() > 1)
{
f.Write( "#CHANGEBPM:" );
for( unsigned i=1; i<out.m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=1; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.Write( ssprintf("%.3f=%.3f", bs.m_iStartRow * 4.0f / ROWS_PER_BEAT, bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
+229
View File
@@ -0,0 +1,229 @@
#include "global.h"
#include "NotesWriterJson.h"
#include "TimingData.h"
#include "json/value.h"
#include "JsonUtil.h"
#include "Song.h"
#include "BackgroundUtil.h"
#include "Steps.h"
#include "NoteData.h"
#include "GameManager.h"
void Serialize(const BPMSegment &seg, Json::Value &root)
{
root["Beat"] = NoteRowToBeat(seg.m_iStartRow);
root["BPM"] = seg.m_fBPS * 60;
}
static void Serialize(const StopSegment &seg, Json::Value &root)
{
root["Beat"] = NoteRowToBeat(seg.m_iStartRow);
root["Seconds"] = seg.m_fStopSeconds;
}
static void Serialize(const TimingData &td, Json::Value &root)
{
JsonUtil::SerializeVectorObjects( td.m_BPMSegments, Serialize, root["BpmSegments"] );
JsonUtil::SerializeVectorObjects( td.m_StopSegments, Serialize, root["StopSegments"] );
}
static void Serialize(const LyricSegment &o, Json::Value &root)
{
root["StartTime"] = (float)o.m_fStartTime;
root["Lyric"] = o.m_sLyric;
root["Color"] = o.m_Color.ToString();
}
static void Serialize(const BackgroundDef &o, Json::Value &root)
{
root["Effect"] = o.m_sEffect;
root["File1"] = o.m_sFile1;
root["File2"] = o.m_sFile2;
root["Color1"] = o.m_sColor1;
}
static void Serialize(const BackgroundChange &o, Json::Value &root )
{
Serialize( o.m_def, root["Def"] );
root["StartBeat"] = o.m_fStartBeat;
root["Rate"] = o.m_fRate;
root["Transition"] = o.m_sTransition;
}
static void Serialize( const TapNote &o, Json::Value &root )
{
root = Json::Value(Json::objectValue);
if( o.type != TapNote::tap )
root["Type"] = (int)o.type;
if( o.type == TapNote::hold_head )
root["SubType"] = (int)o.subType;
//root["Source"] = (int)source;
if( !o.sAttackModifiers.empty() )
root["AttackModifiers"] = o.sAttackModifiers;
if( o.fAttackDurationSeconds > 0 )
root["AttackDurationSeconds"] = o.fAttackDurationSeconds;
if( o.iKeysoundIndex != -1 )
root["KeysoundIndex"] = o.iKeysoundIndex;
if( o.iDuration > 0 )
root["Duration"] = o.iDuration;
if( o.pn != PLAYER_INVALID )
root["PlayerNumber"] = (int)o.pn;
}
static void Serialize( const NoteData &o, Json::Value &root )
{
root = Json::Value(Json::arrayValue);
for(int t=0; t < o.GetNumTracks(); t++ )
{
NoteData::TrackMap::const_iterator begin, end;
o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end );
//NoteData::TrackMap tm = o.GetTrack(t);
//FOREACHM_CONST( int, TapNote, tm, iter )
for( ; begin != end; ++begin )
{
int iRow = begin->first;
TapNote tn = begin->second;
root.resize( root.size()+1 );
Json::Value &root2 = root[ root.size()-1 ];
root2 = Json::Value(Json::arrayValue);
root2.resize(3);
root2[(unsigned)0] = NoteRowToBeat(iRow);
root2[1] = t;
Serialize( tn, root2[2] );
}
}
}
static void Serialize( const RadarValues &o, Json::Value &root )
{
FOREACH_ENUM( RadarCategory, rc )
{
root[ RadarCategoryToString(rc) ] = o.m_Values.f[rc];
}
}
static void Serialize( const Steps &o, Json::Value &root )
{
root["StepsType"] = StringConversion::ToString(o.m_StepsType);
o.Decompress();
NoteData nd;
o.GetNoteData( nd );
Serialize( nd, root["NoteData"] );
root["Hash"] = o.GetHash();
root["Description"] = o.GetDescription();
root["Difficulty"] = DifficultyToString(o.GetDifficulty());
root["Meter"] = o.GetMeter();
Serialize( o.GetRadarValues( PLAYER_1 ), root["RadarValues"] );
}
bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWriteSteps )
{
Json::Value root;
root["SongDir"] = out.GetSongDir();
root["GroupName"] = out.m_sGroupName;
root["Title"] = out.m_sMainTitle;
root["SubTitle"] = out.m_sSubTitle;
root["Artist"] = out.m_sArtist;
root["TitleTranslit"] = out.m_sMainTitleTranslit;
root["SubTitleTranslit"] = out.m_sSubTitleTranslit;
root["Genre"] = out.m_sGenre;
root["Credit"] = out.m_sCredit;
root["Banner"] = out.m_sBannerFile;
root["Background"] = out.m_sBackgroundFile;
root["LyricsFile"] = out.m_sLyricsFile;
root["CDTitle"] = out.m_sCDTitleFile;
root["Music"] = out.m_sMusicFile;
root["Offset"] = out.m_SongTiming.m_fBeat0OffsetInSeconds;
root["SampleStart"] = out.m_fMusicSampleStartSeconds;
root["SampleLength"] = out.m_fMusicSampleLengthSeconds;
if( out.m_SelectionDisplay == Song::SHOW_ALWAYS )
root["Selectable"] = "YES";
else if( out.m_SelectionDisplay == Song::SHOW_NEVER )
root["Selectable"] = "NO";
else
root["Selectable"] = "YES";
root["FirstBeat"] = out.m_fFirstBeat;
root["LastBeat"] = out.m_fLastBeat;
root["SongFileName"] = out.m_sSongFileName;
root["HasMusic"] = out.m_bHasMusic;
root["HasBanner"] = out.m_bHasBanner;
root["MusicLengthSeconds"] = out.m_fMusicLengthSeconds;
root["DisplayBpmType"] = StringConversion::ToString(out.m_DisplayBPMType);
if( out.m_DisplayBPMType == DISPLAY_BPM_SPECIFIED )
{
root["SpecifiedBpmMin"] = out.m_fSpecifiedBPMMin;
root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax;
}
Serialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] );
{
Json::Value &root2 = root["BackgroundChanges"];
FOREACH_BackgroundLayer( bl )
{
Json::Value &root3 = root2[bl];
const vector<BackgroundChange> &vBgc = out.GetBackgroundChanges(bl);
JsonUtil::SerializeVectorObjects( vBgc, Serialize, root3 );
}
}
{
const vector<BackgroundChange> &vBgc = out.GetForegroundChanges();
JsonUtil::SerializeVectorObjects( vBgc, Serialize, root["ForegroundChanges"] );
}
JsonUtil::SerializeArrayValues( out.m_vsKeysoundFile, root["KeySounds"] );
if( bWriteSteps )
{
vector<const Steps*> vpSteps;
FOREACH_CONST( Steps*, out.GetAllSteps(), iter )
{
if( (*iter)->IsAutogen() )
continue;
vpSteps.push_back( *iter );
}
JsonUtil::SerializeVectorPointers<Steps>( vpSteps, Serialize, root["Charts"] );
}
return JsonUtil::WriteFile( root, sFile, false );
}
bool NotesWriterJson::WriteSteps( const RString &sFile, const Steps &out )
{
Json::Value root;
Serialize( out, root );
return JsonUtil::WriteFile( root, sFile, false );
}
/*
* (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.
*/
+40
View File
@@ -0,0 +1,40 @@
/* NotesWriterJson - Writes a Song to a .json file. */
#ifndef NotesWriterJson_H
#define NotesWriterJson_H
class Song;
class Steps;
namespace NotesWriterJson
{
static bool WriteSong( const RString &sFile, const Song &out, bool bWriteSteps );
static bool WriteSteps( const RString &sFile, const Steps &out );
};
#endif
/*
* (c) 2001-2010 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.
*/
+39 -79
View File
@@ -48,7 +48,7 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
* @brief Write out the common tags for .SM files.
* @param f the file in question.
* @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out )
static void WriteGlobalTags( RageFile &f, Song &out )
{
f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) );
f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) );
@@ -63,20 +63,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) );
f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) );
f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) );
{
vector<RString> vs;
FOREACH_ENUM( InstrumentTrack, it )
if( out.HasInstrumentTrack(it) )
vs.push_back( InstrumentTrackToString(it) +
"=" + out.m_sInstrumentTrackFile[it] );
if( !vs.empty() )
{
RString s = join( ",", vs );
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
}
}
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_Timing.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 )
@@ -111,82 +98,55 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
unsigned wSize = out.m_SongTiming.m_WarpSegments.size();
if( wSize > 0 )
{
for( unsigned i=0; i < wSize; i++ )
{
int iRow = out.m_SongTiming.m_WarpSegments[i].m_iStartRow;
float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow);
float fSkip = fBPS * out.m_SongTiming.m_WarpSegments[i].m_fLengthBeats;
StopSegment ss;
ss.m_iStartRow = iRow;
ss.m_fStopSeconds = -fSkip;
ss.m_bDelay = false; // Best to be sure.
out.m_SongTiming.AddStopSegment( ss );
}
}
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
int iRow = fs.m_iStartRow;
float fBeat = NoteRowToBeat(!fs.m_bDelay ? iRow : iRow - 1);
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.PutLine( ssprintf( "%.3f=%.3f", fBeat, fs.m_fStopSeconds ) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
if( fs.m_fStopSeconds < 0 )
{
out.m_SongTiming.m_StopSegments.erase(
out.m_SongTiming.m_StopSegments.begin()+i,
out.m_SongTiming.m_StopSegments.begin()+i+1 );
i--;
}
}
}
f.PutLine( ";" );
f.Write( "#ATTACKS:" );
for( unsigned j = 0; j < out.m_Attacks.size(); j++ )
{
const Attack &a = out.m_Attacks[j];
f.Write( ssprintf( "TIME=%.2f:LEN=%.2f:MODS=%s",
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) );
if( j+1 < out.m_Attacks.size() )
f.Write( ":" );
f.PutLine( "" );
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.3f=%d=%d", NoteRowToBeat(iter->m_iStartRow),
iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.3f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
FOREACH_BackgroundLayer( b )
{
if( b==0 )
@@ -274,11 +234,11 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
RString desc = (USE_CREDIT ? in.GetCredit() : in.GetDescription());
lines.push_back( ssprintf( " %s:", SmEscape(desc).c_str() ) );
lines.push_back( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) );
lines.push_back( ssprintf( " %d:", clamp( in.GetMeter(), MIN_METER, MAX_METER ) ) );
lines.push_back( ssprintf( " %d:", in.GetMeter() ) );
vector<RString> asRadarValues;
// SM files don't use fakes for radar data. Keep it that way.
int categories = NUM_RadarCategory - 1;
// OpenITG simfiles use 11 radar categories.
int categories = 11;
FOREACH_PlayerNumber( pn )
{
const RadarValues &rv = in.GetRadarValues( pn );
@@ -300,7 +260,7 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
return JoinLineList( lines );
}
bool NotesWriterSM::Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave )
bool NotesWriterSM::Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave )
{
int flags = RageFile::WRITE;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace NotesWriterSM
* @param sPath the path to write the file.
* @param out the Song to be written out.
* @return its success or failure. */
bool Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave );
bool Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave );
/**
* @brief Get some contents about the edit file first.
* @param pSong the Song in question.
+131 -131
View File
@@ -42,7 +42,128 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
}
/**
* @brief Write out the common tags for .SM files.
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
// A utility class to write timing tags more easily!
struct TimingTagWriter {
vector<RString> *m_pvsLines;
RString m_sNext;
TimingTagWriter( vector<RString> *pvsLines ): m_pvsLines (pvsLines) { }
void Write( const int row, const char *value )
{
m_pvsLines->push_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) );
m_sNext = ",";
}
void Write( const int row, const float value ) { Write( row, ssprintf( "%.6f", value ) ); }
void Write( const int row, const int value ) { Write( row, ssprintf( "%d", value ) ); }
void Write( const int row, const int a, const int b ) { Write( row, ssprintf( "%d=%d", a, b ) ); }
void Write( const int row, const float a, const float b ) { Write( row, ssprintf( "%.6f=%.6f", a, b) ); }
void Write( const int row, const float a, const float b, const unsigned short c )
{ Write( row, ssprintf( "%.6f=%.6f=%hd", a, b, c) ); }
void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; }
void Finish( ) { m_pvsLines->push_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); }
};
static void GetTimingTags( vector<RString> &lines, TimingData timing, bool bIsSong = false )
{
TimingTagWriter w ( &lines );
timing.TidyUpData();
w.Init( "BPMS" );
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, bs )
w.Write( bs->m_iStartRow, bs->GetBPM() );
w.Finish();
w.Init( "STOPS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( !ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "DELAYS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "WARPS" );
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, ws )
w.Write( ws->m_iStartRow, ws->m_fLengthBeats );
w.Finish();
ASSERT( !timing.m_vTimeSignatureSegments.empty() );
w.Init( "TIMESIGNATURES" );
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
w.Write( iter->m_iStartRow, iter->m_iNumerator, iter->m_iDenominator );
w.Finish();
ASSERT( !timing.m_TickcountSegments.empty() );
w.Init( "TICKCOUNTS" );
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, ts )
w.Write( ts->m_iStartRow, ts->m_iTicks );
w.Finish();
ASSERT( !timing.m_ComboSegments.empty() );
w.Init( "COMBOS" );
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, cs )
w.Write( cs->m_iStartRow, cs->m_iCombo );
w.Finish();
// Song Timing should only have the initial value.
w.Init( "SPEEDS" );
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, ss )
w.Write( ss->m_iStartRow, ss->m_fPercent, ss->m_fWait, ss->m_usMode );
w.Finish();
if( !bIsSong )
{
w.Init( "FAKES" );
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, fs )
w.Write( fs->m_iStartRow, fs->m_fLengthBeats );
w.Finish();
}
w.Init( "LABELS" );
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, ls )
w.Write( ls->m_iStartRow, ls->m_sLabel.c_str() );
w.Finish();
}
static void WriteTimingTags( RageFile &f, const TimingData &timing, bool bIsSong = false )
{
vector<RString> lines;
GetTimingTags( lines, timing, bIsSong );
f.PutLine( JoinLineList( lines ) );
}
/**
* @brief Write out the common tags for .SSC files.
* @param f the file in question.
* @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out )
@@ -74,7 +195,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
}
}
f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_Timing.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 )
@@ -106,105 +227,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
break;
}
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#WARPS:" );
for( unsigned i=0; i<out.m_Timing.m_WarpSegments.size(); i++ )
{
const WarpSegment &ws = out.m_Timing.m_WarpSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(ws.m_iStartRow), ws.m_fEndBeat ) );
if( i != out.m_Timing.m_WarpSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_ComboSegments.empty() );
f.Write( "#COMBOS:" );
for( unsigned i=0; i<out.m_Timing.m_ComboSegments.size(); i++ )
{
const ComboSegment &cs = out.m_Timing.m_ComboSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(cs.m_iStartRow), cs.m_iCombo ) );
if( i != out.m_Timing.m_ComboSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#LABELS:" );
for( unsigned i=0; i<out.m_Timing.m_LabelSegments.size(); i++ )
{
const LabelSegment &ls = out.m_Timing.m_LabelSegments[i];
f.PutLine( ssprintf( "%.6f=%s", NoteRowToBeat(ls.m_iStartRow), ls.m_sLabel.c_str() ) );
if( i != out.m_Timing.m_LabelSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
WriteTimingTags( f, out.m_SongTiming, true );
FOREACH_BackgroundLayer( b )
{
@@ -258,23 +281,6 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ";" );
}
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
/**
* @brief Retrieve the individual batches of NoteData.
* @param song the Song in question.
@@ -294,7 +300,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#DESCRIPTION:%s;", SmEscape(in.GetDescription()).c_str() ) );
lines.push_back( ssprintf( "#CHARTSTYLE:%s;", SmEscape(in.GetChartStyle()).c_str() ) );
lines.push_back( ssprintf( "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty()).c_str() ) );
lines.push_back( ssprintf( "#METER:%d;", clamp( in.GetMeter(), MIN_METER, MAX_METER ) ) );
lines.push_back( ssprintf( "#METER:%d;", in.GetMeter() ) );
vector<RString> asRadarValues;
FOREACH_PlayerNumber( pn )
@@ -307,19 +313,12 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
/*
* TODO: Remove this block, transplant above code
* below for Split Timing. -Wolfman2000 */
lines.push_back( "#BPMS:;" );
lines.push_back( "#STOPS:;" );
lines.push_back( "#DELAYS:;" );
lines.push_back( "#WARPS:;" );
lines.push_back( "#LABELS:;" );
lines.push_back( "#TIMESIGNATURES:;" );
lines.push_back( "#TICKCOUNTS:;" );
GetTimingTags( lines, in.m_Timing );
// For now, attacks are NOT in use for the step.
lines.push_back( "#ATTACKS:;" );
lines.push_back( "#COMBOS:;" );
lines.push_back( ssprintf( "#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds ) );
RString sNoteData;
in.GetSMNoteData( sNoteData );
@@ -350,6 +349,7 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
}
WriteGlobalTags( f, out );
if( bSavingCache )
{
f.PutLine( ssprintf( "// cache tags:" ) );
+3 -3
View File
@@ -154,7 +154,7 @@ public:
RageException::Throw( "Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str() );
m_Def.m_bOneChoiceForAllPlayers = false;
const int NumCols = atoi( lCmds.v[0].m_vsArgs[0] );
const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] );
for( unsigned i=1; i<lCmds.v.size(); i++ )
{
const Command &cmd = lCmds.v[i];
@@ -165,7 +165,7 @@ public:
else if( sName == "selectone" ) m_Def.m_selectType = SELECT_ONE;
else if( sName == "selectnone" ) m_Def.m_selectType = SELECT_NONE;
else if( sName == "showoneinrow" ) m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW;
else if( sName == "default" ) m_Def.m_iDefault = atoi( cmd.GetArg(1).s ) - 1; // match ENTRY_MODE
else if( sName == "default" ) m_Def.m_iDefault = StringToInt( cmd.GetArg(1).s ) - 1; // match ENTRY_MODE
else if( sName == "reloadrowmessages" )
{
for( unsigned a=1; a<cmd.m_vsArgs.size(); a++ )
@@ -177,7 +177,7 @@ public:
for( unsigned a=1; a<cmd.m_vsArgs.size(); a++ )
{
RString sArg = cmd.m_vsArgs[a];
PlayerNumber pn = (PlayerNumber)(atoi(sArg)-1);
PlayerNumber pn = (PlayerNumber)(StringToInt(sArg)-1);
ASSERT( pn >= 0 && pn < NUM_PLAYERS );
m_Def.m_vEnabledForPlayers.insert( pn );
}
+1 -1
View File
@@ -24,7 +24,7 @@ OptionsCursor::OptionsCursor( const OptionsCursor &cpy ):
m_iOriginalCanGoLeftX( cpy.m_iOriginalCanGoLeftX ),
m_iOriginalCanGoRightX( cpy.m_iOriginalCanGoRightX )
{
/* Re-add children, or m_SubActors will point to cpy's children and not our own. */
// Re-add children, or m_SubActors will point to cpy's children and not our own.
m_SubActors.clear();
this->AddChild( m_sprMiddle );
this->AddChild( m_sprLeft );
+2 -2
View File
@@ -28,11 +28,11 @@ protected:
AutoActor m_sprMiddle;
AutoActor m_sprLeft;
AutoActor m_sprRight;
AutoActor m_sprCanGoLeft;
AutoActor m_sprCanGoRight;
// save the metrics-set X because it gets oblitterated on a call to SetBarWidth
// save the metrics-set X because it gets obliterated on a call to SetBarWidth
int m_iOriginalLeftX;
int m_iOriginalRightX;
int m_iOriginalCanGoLeftX;
+44 -33
View File
@@ -506,7 +506,7 @@ void Player::Load()
m_LastTapNoteScore = TNS_None;
// The editor can start playing in the middle of the song.
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );
const int iNoteRow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
m_iFirstUncrossedRow = iNoteRow - 1;
m_pJudgedRows->Reset( iNoteRow );
@@ -541,6 +541,8 @@ void Player::Load()
if( GAMESTATE->m_pCurGame->m_bAllowHopos )
NoteDataUtil::SetHopoPossibleFlags( pSong, m_NoteData );
m_Timing = &GAMESTATE->m_pCurSteps[pn]->m_Timing;
switch( GAMESTATE->m_PlayMode )
{
case PLAY_MODE_RAVE:
@@ -693,7 +695,7 @@ void Player::Update( float fDeltaTime )
return;
}
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
// Optimization: Don't spend time processing the things below that won't show
@@ -795,7 +797,7 @@ void Player::Update( float fDeltaTime )
// Check for a strum miss
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < GAMESTATE->m_fMusicSeconds )
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds )
{
DoStrumMiss();
}
@@ -917,12 +919,12 @@ void Player::Update( float fDeltaTime )
/* We want to send the crossed row message exactly when we cross the row--not
* .5 before the row. Use a very slow song (around 2 BPM) as a test case: without
* rounding, autoplay steps early. -glenn */
const int iRowNow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );
const int iRowNow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
if( iRowNow >= 0 )
{
if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) )
{
if(GAMESTATE->m_bDelay)
if(m_pPlayerState->m_Position.m_bDelay)
{
if( !m_bDelay )
m_bDelay = true;
@@ -1528,7 +1530,7 @@ int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool
// Is this the row we want?
do {
const TapNote &tn = begin->second;
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( begin->first ) )
if( m_Timing->IsWarpAtRow( begin->first ) || m_Timing->IsFakeAtRow( begin->first ) )
break;
if( tn.type == TapNote::empty )
break;
@@ -1579,7 +1581,7 @@ int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool b
++iter;
continue;
}
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) )
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
{
++iter;
continue;
@@ -1650,7 +1652,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
}
// Handle hammer-ons and pull-offs
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
int iHopoCol = -1;
bool bDoHopo =
m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 &&
@@ -1703,7 +1705,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
// Check if this fret breaks all active holds.
if( !bRelease )
{
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
int iMaxHoldCol = -1;
@@ -1745,7 +1747,7 @@ void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRel
DoStrumMiss();
}
m_pPlayerState->m_fLastStrumMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_pPlayerState->m_fLastStrumMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds;
StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged );
}
@@ -1794,7 +1796,7 @@ void Player::ScoreAllActiveHoldsLetGo()
{
if( PENALIZE_TAP_SCORE_NONE )
{
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
// Score all active holds to NotHeld
@@ -1857,8 +1859,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Do everything that depends on a RageTimer here;
// set your breakpoints somewhere after this block.
const float fLastBeatUpdate = GAMESTATE->m_LastBeatUpdate.Ago();
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
const float fLastBeatUpdate = m_pPlayerState->m_Position.m_LastBeatUpdate.Ago();
const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
const float fTimeSinceStep = tm.Ago();
switch( pbt )
@@ -1873,7 +1875,16 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
break;
}
const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;
float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
if( GAMESTATE->m_pCurSong )
{
fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
if( GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber] )
fSongBeat = m_Timing->GetBeatFromElapsedTime( fPositionSeconds );
}
const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row;
if( col != -1 && !bRelease )
@@ -2011,8 +2022,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
* "jack hammers." Hmm.
*/
const int iStepSearchRows = max(
BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds + StepSearchDistance ) ) - iSongRow,
iSongRow - BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds - StepSearchDistance ) )
BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds + StepSearchDistance ) ) - iSongRow,
iSongRow - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds - StepSearchDistance ) )
) + ROWS_PER_BEAT;
int iRowOfOverlappingNoteOrRow = row;
if( row == -1 )
@@ -2039,7 +2050,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
float fNoteOffset = 0.0f;
// we need this later if we are autosyncing
const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow );
const float fStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fStepBeat);
const float fStepSeconds = m_Timing->GetElapsedTimeFromBeat(fStepBeat);
if( row == -1 )
{
@@ -2048,7 +2059,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
* out what the music time is as of now. */
const float fCurrentMusicSeconds = GAMESTATE->m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
const float fCurrentMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
// ... which means it happened at this point in the music:
const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -2089,7 +2100,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Stepped too close to mine?
if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) &&
fSecondsFromExact <= GetWindowSeconds(TW_Mine) &&
!GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iSongRow) )
!m_Timing->IsWarpAtRow(iSongRow) && !m_Timing->IsFakeAtRow(iSongRow))
score = TNS_HitMine;
break;
@@ -2557,14 +2568,14 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
{
//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
int iMissIfOlderThanThisRow;
const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds;
const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds;
{
bool bFreeze, bDelay;
float fMissIfOlderThanThisBeat;
float fThrowAway;
int iWarpBeginRow;
float fWarpLength;
GAMESTATE->m_pCurSong->m_Timing.GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat );
if( bFreeze || bDelay )
@@ -2586,8 +2597,8 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
if( !NeedsTapJudging(tn) )
continue;
// Ignore all notes that are skipped via WARPS.
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) )
// Ignore all notes in WarpSegments or FakeSegments.
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
continue;
if( tn.type == TapNote::mine )
@@ -2610,7 +2621,7 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
void Player::UpdateJudgedRows()
{
const int iEndRow = BeatToNoteRow( GAMESTATE->m_fSongBeat );
const int iEndRow = BeatToNoteRow( m_pPlayerState->m_Position.m_fSongBeat );
bool bAllJudged = true;
const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately;
@@ -2621,8 +2632,8 @@ void Player::UpdateJudgedRows()
{
int iRow = iter.Row();
// If row is within a warp section, ignore it. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iRow) )
// Do not judge arrows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow(iRow) || m_Timing->IsFakeAtRow(iRow) )
continue;
if( iLastSeenRow != iRow )
@@ -2843,13 +2854,13 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
int iCheckpointFrequencyRows = ROWS_PER_BEAT/2;
if( CHECKPOINTS_USE_TICKCOUNTS )
{
int tickCurrent = GAMESTATE->m_pCurSong->m_Timing.GetTickcountAtRow( iLastRowCrossed );
int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed );
// There are some charts that don't want tickcounts involved at all.
iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0);
}
else if( CHECKPOINTS_USE_TIME_SIGNATURES )
{
TimeSignatureSegment tSignature = GAMESTATE->m_pCurSong->m_Timing.GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
TimeSignatureSegment tSignature = m_Timing->GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
// Most songs are in 4/4 time. The frequency for checking tick counts should reflect that.
iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature.m_iDenominator / (tSignature.m_iNumerator * 4);
@@ -2956,8 +2967,8 @@ void Player::HandleTapRowScore( unsigned row )
bNoCheating = false;
#endif
// Warp hackery. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( row ) )
// Do not score rows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow( row ) || m_Timing->IsFakeAtRow( row ) )
return;
if( GAMESTATE->m_bDemonstrationOrJukebox )
@@ -3060,8 +3071,8 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
bNoCheating = false;
#endif
// More warp hackery. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iRow ) )
// WarpSegments and FakeSegments aren't judged in any way.
if( m_Timing->IsWarpAtRow( iRow ) || m_Timing->IsFakeAtRow( iRow ) )
return;
// don't accumulate combo if AutoPlay is on.
@@ -3257,7 +3268,7 @@ void Player::SetCombo( int iCombo, int iMisses )
}
else
{
bPastBeginning = GAMESTATE->m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO;
bPastBeginning = m_pPlayerState->m_Position.m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO;
}
if( m_bSendJudgmentAndComboMessages )
+2
View File
@@ -10,6 +10,7 @@
#include "ScreenMessage.h"
#include "ThemeMetric.h"
#include "InputEventPlus.h"
#include "TimingData.h"
class ScoreDisplay;
class LifeMeter;
@@ -147,6 +148,7 @@ protected:
PlayerState *m_pPlayerState;
/** @brief The player's present stage stats. */
PlayerStageStats *m_pPlayerStageStats;
TimingData *m_Timing;
float m_fNoteFieldHeight;
bool m_bPaused;
+2 -2
View File
@@ -299,7 +299,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
{
/* XXX We know what they want, is there any reason not to handle it? */
/* Yes. We should be strict in handling the format. -Chris */
sErrorOut = ssprintf("Invalid player options \"%s\"; did you mean '*%d'?", s->c_str(), atoi(*s) );
sErrorOut = ssprintf("Invalid player options \"%s\"; did you mean '*%d'?", s->c_str(), StringToInt(*s) );
return false;
}
else
@@ -680,7 +680,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerNumber pn ) const
{
if( m_fTimeSpacing && pSong->HasSignificantBpmChangesOrStops() )
if( m_fTimeSpacing && pSteps->HasSignificantTimingChanges() )
return true;
const RadarValues &rv = pSteps->GetRadarValues( pn );
if( m_bTransforms[TRANSFORM_NOHOLDS] && rv[RadarCategory_Holds]>0 )
+2 -1
View File
@@ -35,8 +35,9 @@ public:
m_fPassmark(0), m_SpeedfPassmark(1.0f),
m_fRandomSpeed(0), m_SpeedfRandomSpeed(1.0f),
m_bMuteOnError(false), m_FailType(FAIL_IMMEDIATE),
m_ScoreDisplay(SCORING_ADD), m_sNoteSkin("")
m_ScoreDisplay(SCORING_ADD)
{
m_sNoteSkin = "";
ZERO( m_fAccels ); ONE( m_SpeedfAccels );
ZERO( m_fEffects ); ONE( m_SpeedfEffects );
ZERO( m_fAppearances ); ONE( m_SpeedfAppearances );
+9 -3
View File
@@ -69,8 +69,8 @@ void PlayerState::Update( float fDelta )
bool bCurrentlyEnabled =
attack.bGlobal ||
( attack.fStartSecond < GAMESTATE->m_fMusicSeconds &&
GAMESTATE->m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining );
( attack.fStartSecond < m_Position.m_fMusicSeconds &&
m_Position.m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining );
if( m_ActiveAttacks[s].bOn == bCurrentlyEnabled )
continue; // OK
@@ -116,7 +116,7 @@ void PlayerState::LaunchAttack( const Attack& a )
* so Player::Update knows to apply attack transforms correctly. (yuck) */
m_ModsToApply.push_back( attack );
if( attack.fStartSecond == -1 )
attack.fStartSecond = GAMESTATE->m_fMusicSeconds;
attack.fStartSecond = m_Position.m_fMusicSeconds;
m_ActiveAttacks.push_back( attack );
RebuildPlayerOptionsFromActiveAttacks();
@@ -197,6 +197,11 @@ class LunaPlayerState: public Luna<PlayerState>
{
public:
DEFINE_METHOD( GetPlayerNumber, m_PlayerNumber );
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetMultiPlayerNumber, m_mp );
DEFINE_METHOD( GetPlayerController, m_PlayerController );
static int SetPlayerOptions( T* p, lua_State *L )
@@ -246,6 +251,7 @@ public:
ADD_METHOD( GetPlayerOptionsArray );
ADD_METHOD( GetPlayerOptionsString );
ADD_METHOD( GetCurrentPlayerOptions );
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetHealthState );
}
};
+4
View File
@@ -3,6 +3,7 @@
#ifndef PlayerState_H
#define PlayerState_H
#include "SongPosition.h"
#include "Attack.h"
#include "ModsGroup.h"
#include "PlayerNumber.h"
@@ -38,6 +39,9 @@ public:
*/
MultiPlayer m_mp;
// Music statistics:
SongPosition m_Position;
/**
* @brief Change the PlayerOptions to their default.
* @param l the level of mods to reset.
+1 -1
View File
@@ -1922,7 +1922,7 @@ RString Profile::MakeUniqueFileNameNoExtension( RString sDir, RString sFileNameB
continue;
ASSERT( matches.size() == 1 );
iIndex = atoi( matches[0] )+1;
iIndex = StringToInt( matches[0] )+1;
break;
}
+3 -1
View File
@@ -429,7 +429,7 @@ bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut )
vector<RString> vs;
GetLocalProfileIDs( vs );
FOREACH_CONST( RString, vs, s )
iMaxProfileNumber = atoi( *s );
iMaxProfileNumber = StringToInt( *s );
int iProfileNumber = iMaxProfileNumber + 1;
RString sProfileID = ssprintf( "%08d", iProfileNumber );
@@ -833,6 +833,7 @@ public:
static int GetNumLocalProfiles( T* p, lua_State *L ) { lua_pushnumber(L, p->GetNumLocalProfiles() ); return 1; }
static int GetProfileDir( T* p, lua_State *L ) { lua_pushstring(L, p->GetProfileDir(Enum::Check<ProfileSlot>(L, 1)) ); return 1; }
static int IsSongNew( T* p, lua_State *L ) { lua_pushboolean(L, p->IsSongNew(Luna<Song>::check(L,1)) ); return 1; }
static int ProfileWasLoadedFromMemoryCard( T* p, lua_State *L ) { lua_pushboolean(L, p->ProfileWasLoadedFromMemoryCard(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
LunaProfileManager()
{
@@ -847,6 +848,7 @@ public:
ADD_METHOD( GetNumLocalProfiles );
ADD_METHOD( GetProfileDir );
ADD_METHOD( IsSongNew );
ADD_METHOD( ProfileWasLoadedFromMemoryCard );
}
};
+2 -2
View File
@@ -26,8 +26,8 @@ static void GetResolutionFromFileName( RString sPath, int &iWidth, int &iHeight
if( !re.Compare(sPath, asMatches) )
return;
iWidth = atoi( asMatches[0].c_str() );
iHeight = atoi( asMatches[1].c_str() );
iWidth = StringToInt( asMatches[0] );
iHeight = StringToInt( asMatches[1] );
}
RageBitmapTexture::RageBitmapTexture( RageTextureID name ) :
+2 -2
View File
@@ -7,9 +7,9 @@
#include <memory>
#if defined(_WINDOWS)
#include "../extern/zlib/zlib.h"
#include "zlib.h"
#if defined(_MSC_VER)
#pragma comment(lib, "../extern/zlib/zdll.lib")
#pragma comment(lib, "zdll.lib")
#endif
#elif defined(MACOSX)
/* Since crypto++ was added to the repository, <zlib.h> includes the zlib.h
+1 -1
View File
@@ -51,7 +51,7 @@ RageSound::RageSound():
m_Mutex( "RageSound" ), m_pSource(NULL),
m_sFilePath(""), m_Param(), m_iStreamFrame(0),
m_iStoppedSourceFrame(0), m_bPlaying(false),
m_sError(""), m_bDeleteWhenFinished(false)
m_bDeleteWhenFinished(false), m_sError("")
{
ASSERT( SOUNDMAN );
}
+1 -1
View File
@@ -12,7 +12,7 @@
#if defined(_WINDOWS) || defined(MACOSX)
#include "../extern/mad-0.15.1b/mad.h"
#ifdef _MSC_VER
#pragma comment(lib, "../extern/mad-0.15.1b/msvc++/Release/libmad.lib")
#pragma comment(lib, "libmad.lib")
#endif //_MSC_VER
#else
#include <mad.h>
+3 -3
View File
@@ -11,11 +11,11 @@
// work around namespace bugs in win32/libjpeg:
#define XMD_H
#undef FAR
#include "../extern/libjpeg/jpeglib.h"
#include "../extern/libjpeg/jerror.h"
#include "jpeglib.h"
#include "jerror.h"
#if defined(_MSC_VER)
#pragma comment(lib, "../extern/libjpeg/jpeg.lib")
#pragma comment(lib, "jpeg.lib")
#endif
#pragma warning(disable: 4611) /* interaction between '_setjmp' and C++ object destruction is non-portable */
+2 -2
View File
@@ -7,9 +7,9 @@
#if defined(_WINDOWS)
# include "../extern/libpng/include/png.h"
# include "png.h"
# if defined(_MSC_VER)
# pragma comment(lib, "../extern/libpng/lib/libpng.lib")
# pragma comment(lib, "libpng.lib")
# pragma warning(disable: 4611) /* interaction between '_setjmp' and C++ object destruction is non-portable */
# endif // _MSC_VER
#else
+1 -1
View File
@@ -18,7 +18,7 @@ namespace jpeg
// Pull in JPEG library here.
#if defined _MSC_VER
#pragma comment(lib, "../extern/libjpeg/jpeg.lib")
#pragma comment(lib, "jpeg.lib")
#endif
#define OUTPUT_BUFFER_SIZE 4096
+2 -2
View File
@@ -7,9 +7,9 @@
#include "RageUtil.h"
#if defined(WINDOWS)
#include "../extern/libpng/include/png.h"
#include "png.h"
#if defined(_MSC_VER)
# pragma comment(lib, "../extern/libpng/lib/libpng.lib")
# pragma comment(lib, "libpng.lib")
#pragma warning(disable: 4611) /* interaction between '_setjmp' and C++ object destruction is non-portable */
#endif
#else
+2 -2
View File
@@ -51,8 +51,8 @@ void RageTexture::GetFrameDimensionsFromFileName( RString sPath, int* piFramesWi
*piFramesWide = *piFramesHigh = 1;
return;
}
*piFramesWide = atoi(asMatch[0]);
*piFramesHigh = atoi(asMatch[1]);
*piFramesWide = StringToInt(asMatch[0]);
*piFramesHigh = StringToInt(asMatch[1]);
}
const RectF *RageTexture::GetTextureCoordRect( int iFrameNo ) const
+18 -3
View File
@@ -10,6 +10,7 @@
#include <numeric>
#include <ctime>
#include <sstream>
#include <map>
#include <sys/types.h>
#include <sys/stat.h>
@@ -193,8 +194,8 @@ float HHMMSSToSeconds( const RString &sHHMMSS )
arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits
float fSeconds = 0;
fSeconds += atoi( arrayBits[0] ) * 60 * 60;
fSeconds += atoi( arrayBits[1] ) * 60;
fSeconds += StringToInt( arrayBits[0] ) * 60 * 60;
fSeconds += StringToInt( arrayBits[1] ) * 60;
fSeconds += StringToFloat( arrayBits[2] );
return fSeconds;
@@ -1695,6 +1696,20 @@ void MakeLower( wchar_t *p, size_t iLen )
UnicodeUpperLower( p, iLen, g_LowerCase );
}
int StringToInt( const RString &sString )
{
int ret;
istringstream ( sString ) >> ret;
return ret;
}
RString IntToString( const int &iNum )
{
stringstream ss;
ss << iNum;
return ss.str();
}
float StringToFloat( const RString &sString )
{
float ret = strtof( sString, NULL );
@@ -2141,7 +2156,7 @@ namespace StringConversion
if( sValue.size() == 0 )
return false;
out = (atoi(sValue) != 0);
out = (StringToInt(sValue) != 0);
return true;
}
+10
View File
@@ -405,6 +405,16 @@ void MakeUpper( char *p, size_t iLen );
void MakeLower( char *p, size_t iLen );
void MakeUpper( wchar_t *p, size_t iLen );
void MakeLower( wchar_t *p, size_t iLen );
/**
* @brief Have a standard way of converting Strings to integers.
* @param sString the string to convert.
* @return the integer we are after. */
int StringToInt( const RString &sString );
/**
* @brief Have a standard way of converting integers to Strings.
* @param iNum the integer to convert.
* @return the string we are after. */
RString IntToString( const int &iNum );
float StringToFloat( const RString &sString );
bool StringToFloat( const RString &sString, float &fOut );
+4 -4
View File
@@ -360,14 +360,14 @@ void ScoreKeeperNormal::AddScoreInternal( TapNoteScore score )
}
ASSERT( iScore >= 0 );
ASSERT_M( iScore >= 0, "iScore < 0 before re-rounding" );
// Undo rounding from the last tap, and re-round.
iScore += m_iScoreRemainder;
m_iScoreRemainder = (iScore % m_iRoundTo);
iScore = iScore - m_iScoreRemainder;
ASSERT( iScore >= 0 );
ASSERT_M( iScore >= 0, "iScore < 0 after re-rounding" );
// LOG->Trace( "score: %i", iScore );
}
@@ -443,7 +443,7 @@ void ScoreKeeperNormal::HandleComboInternal( int iNumHitContinueCombo, int iNumH
if( iNumBreakCombo == 0 )
{
TimingData td = GAMESTATE->m_pCurSong->m_Timing;
TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumHitContinueCombo * multiplier;
}
@@ -463,7 +463,7 @@ void ScoreKeeperNormal::HandleRowComboInternal( TapNoteScore tns, int iNumTapsIn
if ( tns >= m_MinScoreToContinueCombo )
{
m_pPlayerStageStats->m_iCurMissCombo = 0;
TimingData td = GAMESTATE->m_pCurSong->m_Timing;
TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumTapsInRow * multiplier;
}
+539 -306
View File
File diff suppressed because it is too large Load Diff
+76 -29
View File
@@ -71,8 +71,8 @@ enum EditButton
EDIT_BUTTON_SCROLL_NEXT,
EDIT_BUTTON_SCROLL_PREV,
EDIT_BUTTON_LABEL_NEXT,
EDIT_BUTTON_LABEL_PREV,
EDIT_BUTTON_LABEL_NEXT, /**< Jump to the start of the next label downward. */
EDIT_BUTTON_LABEL_PREV, /**< Jump to the start of the previous label upward. */
// These are modifiers to EDIT_BUTTON_SCROLL_*.
EDIT_BUTTON_SCROLL_SELECT,
@@ -86,6 +86,7 @@ enum EditButton
EDIT_BUTTON_SNAP_PREV,
EDIT_BUTTON_OPEN_EDIT_MENU,
EDIT_BUTTON_OPEN_TIMING_MENU,
EDIT_BUTTON_OPEN_AREA_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU,
@@ -128,8 +129,7 @@ enum EditButton
EDIT_BUTTON_SAMPLE_LENGTH_UP,
EDIT_BUTTON_SAMPLE_LENGTH_DOWN,
// This modifies offset, BPM, and stop segment changes.
EDIT_BUTTON_ADJUST_FINE,
EDIT_BUTTON_ADJUST_FINE, /**< This button modifies offset, BPM, and stop segment changes. */
EDIT_BUTTON_SAVE, /**< Save the present changes into the chart. */
@@ -138,6 +138,8 @@ enum EditButton
EDIT_BUTTON_ADD_COURSE_MODS,
EDIT_BUTTON_SWITCH_PLAYERS, /**< Allow entering notes for a different Player. */
EDIT_BUTTON_SWITCH_TIMINGS, /**< Allow switching between Song and Step TimingData. */
NUM_EditButton, // leave this at the end
EditButton_Invalid
@@ -166,7 +168,9 @@ struct MapEditToDI
}
};
// Like MapEditToDI, but maps GameButton instead of DeviceInput.
/**
* @brief This is similar to MapEditToDI,
* but maps GameButton instead of DeviceInput. */
struct MapEditButtonToMenuButton
{
GameButton button[NUM_EditButton][NUM_EDIT_TO_MENU_SLOTS];
@@ -214,11 +218,14 @@ protected:
// Call this before modifying m_NoteDataEdit.
void SaveUndo();
// Revert m_NoteDataEdit using m_Undo.
/** @brief Revert the last change made to m_NoteDataEdit. */
void Undo();
/** @brief Remove the previously stored NoteData to prevent undoing. */
void ClearUndo();
// Call this after modifying m_NoteDataEdit. It will Undo() if
// MAX_NOTES_PER_MEASURE was exceeded.
/**
* @brief This is to be called after modifying m_NoteDataEdit.
*
* It will Undo itself if MAX_NOTES_PER_MEASURE was exceeded. */
void CheckNumberOfNotesAndUndo();
void OnSnapModeChange();
@@ -227,6 +234,9 @@ protected:
float GetMaximumBeatForMoving() const; // don't allow Down key to go past this beat.
void DoHelp();
/** @brief Display the TimingData menu for editing song and step timing. */
void DisplayTimingMenu();
EditState m_EditState;
@@ -252,7 +262,10 @@ protected:
// keep track of where we are and what we're doing
float m_fTrailingBeat; // this approaches GAMESTATE->m_fSongBeat, which is the actual beat
// The location we were at when shift was pressed, or -1 when shift isn't pressed:
/**
* @brief The location we were at when shift was pressed.
*
* If shift wasn't pressed, this will be -1. */
int m_iShiftAnchor;
/** @brief The NoteData that has been cut or copied. */
@@ -268,14 +281,22 @@ protected:
/** @brief Has the NoteData been changed such that a user should be prompted to save? */
bool m_bDirty;
/** @brief The sound that is played when a note is added. */
RageSound m_soundAddNote;
/** @brief The sound that is played when a note is removed. */
RageSound m_soundRemoveNote;
RageSound m_soundChangeLine;
RageSound m_soundChangeSnap;
RageSound m_soundMarker;
RageSound m_soundValueIncrease;
RageSound m_soundValueDecrease;
/** @brief The sound that is played when switching players for Routine. */
RageSound m_soundSwitchPlayer;
/** @brief The sound that is played when switching song/step timing. */
RageSound m_soundSwitchTiming;
/** @brief The sound that is played when switching to a different chart. */
RageSound m_soundSwitchSteps;
/** @brief The sound that is played when the chart is saved. */
RageSound m_soundSave;
// used for reverting
@@ -311,22 +332,23 @@ protected:
ThemeMetric<EditMode> EDIT_MODE;
public:
/** @brief What are the choices that one can make on the main menu? */
enum MainMenuChoice
{
play_selection,
set_selection_start,
set_selection_end,
edit_steps_information,
play_whole_song,
play_whole_song, /**< Play the entire chart from the beginning. */
play_selection_start_to_end,
play_current_beat_to_end,
save,
save, /**< Save the current chart to disk. */
revert_to_last_save,
revert_from_disk,
options,
edit_song_info,
edit_timing_data,
play_preview_music,
options, /**< Modify the PlayerOptions and SongOptions. */
edit_song_info, /**< Edit some general information about the song. */
edit_timing_data, /**< Edit the chart's timing data. */
play_preview_music, /**< Play the song's preview music. */
exit,
save_on_exit,
NUM_MAIN_MENU_CHOICES,
@@ -361,13 +383,14 @@ public:
};
void HandleAreaMenuChoice( AreaMenuChoice c, const vector<int> &iAnswers, bool bAllowUndo = true );
void HandleAreaMenuChoice( AreaMenuChoice c, bool bAllowUndo = true ) { const vector<int> v; HandleAreaMenuChoice( c, v, bAllowUndo ); }
/** @brief How should the selected notes be transformed? */
enum TurnType
{
left,
right,
mirror,
shuffle,
super_shuffle,
left, /**< Turn the notes as if you were facing to the left. */
right, /**< Turn the notes as if you were facing to the right. */
mirror, /**< Turn the notes as if you were facing away from the machine. */
shuffle, /**< Replace one column with another column. */
super_shuffle, /**< Replace each note individually. */
NUM_TURN_TYPES
};
enum TransformType
@@ -421,10 +444,10 @@ public:
{
difficulty,
meter,
description,
chartstyle,
step_credit,
predict_meter,
description, /**< What is the description of this chart? */
chartstyle, /**< How is this chart meant to be played? */
step_credit, /**< Who wrote this individual chart? */
predict_meter, /**< What does the game think this chart's rating should be? */
tap_notes,
jumps,
hands,
@@ -450,7 +473,6 @@ public:
main_title_transliteration,
sub_title_transliteration,
artist_transliteration,
beat_0_offset,
last_beat_hint,
preview_start,
preview_length,
@@ -463,16 +485,21 @@ public:
enum TimingDataInformationChoice
{
beat_0_offset,
bpm,
stop,
delay,
// time_signature,
time_signature_numerator,
time_signature_denominator,
time_signature,
label,
tickcount,
combo,
label,
warp,
// speed,
speed_percent,
speed_wait,
speed_mode,
fake,
erase_step_timing,
NUM_TIMING_DATA_INFORMATION_CHOICES
};
@@ -505,7 +532,19 @@ public:
delete_change,
NUM_BGCHANGE_CHOICES
};
enum SpeedSegmentModes
{
SSMODE_Beats,
SSMODE_Seconds
};
/**
* @brief Take care of any background changes that the user wants.
*
* It is important that this is only called in Song Timing mode.
* @param c the Background Change style requested.
* @param iAnswers the other settings involving the change. */
void HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers );
enum CourseAttackChoice
@@ -535,6 +574,14 @@ public:
void MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu );
void EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK = SM_None, ScreenMessage SM_SendOnCancel = SM_None );
private:
/**
* @brief Retrieve the appropriate TimingData based on GAMESTATE.
* @return the proper TimingData. */
TimingData & GetAppropriateTiming() const;
void SetBeat(float fBeat);
float GetBeat();
};
#endif

Some files were not shown because too many files have changed in this diff Show More