[default -> Xcode4] Lion is approaching.

This commit is contained in:
Jason Felds
2011-07-20 10:12:21 -04:00
400 changed files with 29179 additions and 6031 deletions
+3 -3
View File
@@ -105,9 +105,9 @@ void Actor::InitState()
#endif
m_fSecsIntoEffect = 0;
m_fEffectDelta = 0;
m_fEffectRampUp = 0.5;
m_fEffectRampUp = 0.5f;
m_fEffectHoldAtHalf = 0;
m_fEffectRampDown = 0.5;
m_fEffectRampDown = 0.5f;
m_fEffectHoldAtZero = 0;
m_fEffectOffset = 0;
m_EffectClock = CLOCK_TIMER;
@@ -118,7 +118,7 @@ void Actor::InitState()
m_bVisible = true;
m_fShadowLengthX = 0;
m_fShadowLengthY = 0;
m_ShadowColor = RageColor(0,0,0,0.5);
m_ShadowColor = RageColor(0,0,0,0.5f);
m_bIsAnimating = true;
m_fHibernateSecondsLeft = 0;
m_iDrawOrder = 0;
+28 -11
View File
@@ -256,7 +256,7 @@ void AdjustSync::AutosyncTempo()
// keep only a fraction of the data, such as the 80% with the lowest
// error. However, throwing away the ones with high error should
// be enough in most cases.
float fFilteredError = 0.0;
float fFilteredError = 0;
s_iStepsFiltered = s_vAutosyncTempoData.size();
FilterHighErrorPoints( s_vAutosyncTempoData, fSlope, fIntercept, ERROR_TOO_HIGH );
s_iStepsFiltered -= s_vAutosyncTempoData.size();
@@ -266,14 +266,23 @@ void AdjustSync::AutosyncTempo()
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fIntercept;
const float fScaleBPM = 1.0f/(1.0f - fSlope);
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments, i )
i->SetBPM( i->GetBPM() * fScaleBPM );
TimingData &timing = GAMESTATE->m_pCurSong->m_SongTiming;
vector<TimingSegment *> &bpms = timing.allTimingSegments[SEGMENT_BPM];
for (unsigned i = 0; i < bpms.size(); i++)
{
BPMSegment *b = static_cast<BPMSegment *>(bpms[i]);
b->SetBPM(b->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_SongTiming.m_StopSegments, i )
i->SetPause(i->GetPause() * (1.0f - fSlope));
vector<TimingSegment *> &stops = timing.allTimingSegments[SEGMENT_STOP_DELAY];
for (unsigned i = 0; i < stops.size(); i++)
{
StopSegment *s = static_cast<StopSegment *>(stops[i]);
s->SetPause(s->GetPause() * (1.0f - fSlope));
}
SCREENMAN->SystemMessage( AUTOSYNC_CORRECTION_APPLIED.GetValue() );
}
@@ -343,10 +352,14 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i< testing.m_BPMSegments.size(); i++ )
vector<TimingSegment *> &bpmTest = testing.allTimingSegments[SEGMENT_BPM];
vector<TimingSegment *> &bpmOrig = original.allTimingSegments[SEGMENT_BPM];
for( unsigned i=0; i< bpmTest.size(); i++ )
{
float fOld = Quantize( original.m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( testing.m_BPMSegments[i].GetBPM(), 0.001f );
BPMSegment *bT = static_cast<BPMSegment *>(bpmTest[i]);
BPMSegment *bO = static_cast<BPMSegment *>(bpmOrig[i]);
float fOld = Quantize( bO->GetBPM(), 0.001f );
float fNew = Quantize( bT->GetBPM(), 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
@@ -364,10 +377,14 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i< testing.m_StopSegments.size(); i++ )
vector<TimingSegment *> &stopTest = testing.allTimingSegments[SEGMENT_STOP_DELAY];
vector<TimingSegment *> &stopOrig = original.allTimingSegments[SEGMENT_STOP_DELAY];
for( unsigned i=0; i< stopTest.size(); i++ )
{
float fOld = Quantize( original.m_StopSegments[i].GetPause(), 0.001f );
float fNew = Quantize( testing.m_StopSegments[i].GetPause(), 0.001f );
StopSegment *sT = static_cast<StopSegment *>(stopTest[i]);
StopSegment *sO = static_cast<StopSegment *>(stopOrig[i]);
float fOld = Quantize( sO->GetPause(), 0.001f );
float fNew = Quantize( sT->GetPause(), 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
+20 -15
View File
@@ -56,10 +56,12 @@ static ThemeMetric<float> DRUNK_OFFSET_FREQUENCY( "ArrowEffects", "DrunkOffsetFr
static ThemeMetric<float> DRUNK_ARROW_MAGNITUDE( "ArrowEffects", "DrunkArrowMagnitude" );
static ThemeMetric<float> BEAT_OFFSET_HEIGHT( "ArrowEffects", "BeatOffsetHeight" );
static ThemeMetric<float> BEAT_PI_HEIGHT( "ArrowEffects", "BeatPIHeight" );
static ThemeMetric<float> MINI_PERCENT_BASE( "ArrowEffects", "MiniPercentBase" );
static ThemeMetric<float> MINI_PERCENT_GATE( "ArrowEffects", "MiniPercentGate" );
static ThemeMetric<float> TINY_PERCENT_BASE( "ArrowEffects", "TinyPercentBase" );
static ThemeMetric<float> TINY_PERCENT_GATE( "ArrowEffects", "TinyPercentGate" );
static ThemeMetric<bool> DIZZY_HOLD_HEADS( "ArrowEffects", "DizzyHoldHeads" );
float ArrowGetPercentVisible( const PlayerState* pPlayerState, float fYPosWithoutReverse );
static float GetNoteFieldHeight( const PlayerState* pPlayerState )
{
return SCREEN_HEIGHT + fabsf(pPlayerState->m_PlayerOptions.GetCurrent().m_fPerspectiveTilt)*200;
@@ -236,7 +238,10 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
if( bShowEffects )
fBeatsUntilStep = pCurSteps->m_Timing.GetDisplayedBeat(fNoteBeat) - pCurSteps->m_Timing.GetDisplayedBeat(fSongBeat);
float fYOffsetBeatSpacing = fBeatsUntilStep;
float fSpeedMultiplier = bShowEffects ? pCurSteps->m_Timing.GetDisplayedSpeedPercent( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible ) : 1.0;
float fSpeedMultiplier = bShowEffects ?
pCurSteps->m_Timing.GetDisplayedSpeedPercent(
position.m_fSongBeatVisible,
position.m_fMusicSecondsVisible ) : 1.0f;
fYOffset += fSpeedMultiplier * fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
}
@@ -340,8 +345,8 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
static void ArrowGetReverseShiftAndScale( const PlayerState* pPlayerState, int iCol, float fYReverseOffsetPixels, float &fShiftOut, float &fScaleOut )
{
// XXX: Hack: we need to scale the reverse shift by the zoom.
float fTinyPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY];
float fZoom = 1 - fTinyPercent*0.5f;
float fMiniPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI];
float fZoom = 1 - fMiniPercent*0.5f;
// don't divide by 0
if( fabsf(fZoom) < 0.01 )
@@ -484,12 +489,12 @@ float ArrowEffects::GetXPos( const PlayerState* pPlayerState, int iColNum, float
fPixelOffsetFromCenter += pCols[iColNum].fXOffset;
if( fEffects[PlayerOptions::EFFECT_MINI] != 0 )
if( fEffects[PlayerOptions::EFFECT_TINY] != 0 )
{
// Allow Mini to pull tracks together, but not to push them apart.
float fMiniPercent = fEffects[PlayerOptions::EFFECT_MINI];
fMiniPercent = min( powf(MINI_PERCENT_BASE, fMiniPercent), (float)MINI_PERCENT_GATE );
fPixelOffsetFromCenter *= fMiniPercent;
// Allow Tiny to pull tracks together, but not to push them apart.
float fTinyPercent = fEffects[PlayerOptions::EFFECT_TINY];
fTinyPercent = min( powf(TINY_PERCENT_BASE, fTinyPercent), (float)TINY_PERCENT_GATE );
fPixelOffsetFromCenter *= fTinyPercent;
}
return fPixelOffsetFromCenter;
@@ -560,7 +565,7 @@ static float GetCenterLine( const PlayerState* pPlayerState )
{
/* Another mini hack: if EFFECT_MINI is on, then our center line is at
* eg. 320, not 160. */
const float fMiniPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY];
const float fMiniPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI];
const float fZoom = 1 - fMiniPercent*0.5f;
return CENTER_LINE_Y / fZoom;
}
@@ -736,11 +741,11 @@ float ArrowEffects::GetZoom( const PlayerState* pPlayerState )
(GAMESTATE->GetNumSidesJoined()==2 || GAMESTATE->AnyPlayersAreCpu()) )
fZoom *= 0.6f;
float fMiniPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI];
if( fMiniPercent != 0 )
float fTinyPercent = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY];
if( fTinyPercent != 0 )
{
fMiniPercent = powf( 0.5f, fMiniPercent );
fZoom *= fMiniPercent;
fTinyPercent = powf( 0.5f, fTinyPercent );
fZoom *= fTinyPercent;
}
return fZoom;
}
+21
View File
@@ -91,6 +91,27 @@ bool AttackArray::ContainsTransformOrTurn() const
return false;
}
vector<RString> AttackArray::ToVectorString() const
{
vector<RString> ret;
FOREACH_CONST( Attack, *this, a )
{
ret.push_back(ssprintf("TIME=%f:LEN=%f:MODS=%s",
a->fStartSecond,
a->fSecsRemaining,
a->sModifiers.c_str()));
}
return ret;
}
void AttackArray::UpdateStartTimes(float delta)
{
FOREACH(Attack, *this, a)
{
a->fStartSecond += delta;
}
}
/*
* (c) 2003-2004 Chris Danford
* All rights reserved.
+10
View File
@@ -71,6 +71,16 @@ struct AttackArray : public vector<Attack>
* @brief Determine if the list of attacks contains a transform or turn mod.
* @return true if it does, or false otherwise. */
bool ContainsTransformOrTurn() const;
/**
* @brief Return a string representation used for simfiles.
* @return the string representation. */
vector<RString> ToVectorString() const;
/**
* @brief Adjust the starting time of all attacks.
* @param delta the amount to change. */
void UpdateStartTimes(float delta);
};
#endif
+2
View File
@@ -6,6 +6,8 @@
#include "GameConstantsAndTypes.h" // for TapNoteScore
#include "RageTexturePreloader.h"
RString GetAttackPieceName( const RString &sAttack );
class PlayerState;
/** @brief A graphical display for attacks. */
class AttackDisplay : public ActorFrame
+7 -3
View File
@@ -49,7 +49,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain )
* Add all current autoplay sounds in both players to the chain. If a sound is
* common to both players, don't pan it; otherwise pan it to that player's side.
*/
int iNumTracks = m_ndAutoKeysoundsOnly[GAMESTATE->m_MasterPlayerNumber].GetNumTracks();
int iNumTracks = m_ndAutoKeysoundsOnly[GAMESTATE->GetMasterPlayerNumber()].GetNumTracks();
for( int t = 0; t < iNumTracks; t++ )
{
int iRow = -1;
@@ -63,6 +63,10 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain )
if( t >= m_ndAutoKeysoundsOnly[pn].GetNumTracks() )
continue;
int iNextRowForPlayer = iRow;
/* XXX: If a BMS file only has one tap note per track,
* this will prevent any keysounds from loading.
* This leads to failure later on.
* We need a better way to prevent this. */
if( m_ndAutoKeysoundsOnly[pn].GetNextTapNoteRowForTrack( t, iNextRowForPlayer ) )
iNextRow = min( iNextRow, iNextRowForPlayer );
}
@@ -274,7 +278,7 @@ void AutoKeysounds::FinishLoading()
delete pChain;
}
}
ASSERT( m_pSharedSound );
ASSERT_M( m_pSharedSound, ssprintf("No keysounds were loaded for the song %s!", pSong->m_sMainTitle.c_str() ));
m_pSharedSound = new RageSoundReader_PitchChange( m_pSharedSound );
m_pSharedSound = new RageSoundReader_PostBuffering( m_pSharedSound );
@@ -297,7 +301,7 @@ void AutoKeysounds::FinishLoading()
apSounds.push_back( m_pPlayerSounds[1] );
}
if( GAMESTATE->GetNumPlayersEnabled() == 1 && GAMESTATE->m_MasterPlayerNumber == PLAYER_2 )
if( GAMESTATE->GetNumPlayersEnabled() == 1 && GAMESTATE->GetMasterPlayerNumber() == PLAYER_2 )
swap( m_pPlayerSounds[PLAYER_1], m_pPlayerSounds[PLAYER_2] );
if( apSounds.size() > 1 )
+25 -18
View File
@@ -27,7 +27,6 @@ static ThemeMetric<float> LEFT_EDGE ("Background","LeftEdge");
static ThemeMetric<float> TOP_EDGE ("Background","TopEdge");
static ThemeMetric<float> RIGHT_EDGE ("Background","RightEdge");
static ThemeMetric<float> BOTTOM_EDGE ("Background","BottomEdge");
#define RECT_BACKGROUND RectF (LEFT_EDGE,TOP_EDGE,RIGHT_EDGE,BOTTOM_EDGE)
static ThemeMetric<float> CLAMP_OUTPUT_PERCENT ("Background","ClampOutputPercent");
static ThemeMetric<bool> SHOW_DANCING_CHARACTERS ("Background","ShowDancingCharacters");
static ThemeMetric<bool> USE_STATIC_BG ("Background","UseStaticBackground");
@@ -422,35 +421,41 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
const TimingData &timing = m_pSong->m_SongTiming;
// change BG every time signature change or 4 measures
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
const vector<TimingSegment *> &tSigs = timing.allTimingSegments[SEGMENT_TIME_SIG];
for (unsigned i = 0; i < tSigs.size(); i++)
{
vector<TimeSignatureSegment>::const_iterator next = iter;
next++;
int iSegmentEndRow = (next == timing.m_vTimeSignatureSegments.end()) ? iEndRow : next->GetRow();
TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]);
int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow();
for( int i=max(iter->GetRow(),iStartRow); i<min(iEndRow,iSegmentEndRow); i+=4*iter->GetNoteRowsPerMeasure() )
for(int j=max(ts->GetRow(),iStartRow);
j<min(iEndRow,iSegmentEndRow);
j+=4*ts->GetNoteRowsPerMeasure())
{
// Don't fade. It causes frame rate dip, especially on slower machines.
BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this );
BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong,
change.m_def.m_sEffect,
m_RandomBGAnimations, this);
if( !bd.IsEmpty() )
{
BackgroundChange c = change;
c.m_def = bd;
c.m_fStartBeat = NoteRowToBeat(i);
c.m_fStartBeat = NoteRowToBeat(j);
m_Layer[0].m_aBGChanges.push_back( c );
}
}
}
// change BG every BPM change that is at the beginning of a measure
for( unsigned i=0; i<timing.m_BPMSegments.size(); i++ )
const vector<TimingSegment *> &bpms = timing.allTimingSegments[SEGMENT_BPM];
for( unsigned i=0; i<bpms.size(); i++ )
{
const BPMSegment& bpmseg = timing.m_BPMSegments[i];
bool bAtBeginningOfMeasure = false;
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
for (unsigned j=0; j<tSigs.size(); j++)
{
if( (bpmseg.GetRow() - iter->GetRow()) % iter->GetNoteRowsPerMeasure() == 0 )
TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[j]);
if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0)
{
bAtBeginningOfMeasure = true;
break;
@@ -461,7 +466,7 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
continue; // skip
// start so that we don't create a BGChange right on top of fEndBeat
bool bInRange = bpmseg.GetRow() >= iStartRow && bpmseg.GetRow() < iEndRow;
bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow;
if( !bInRange )
continue; // skip
@@ -471,7 +476,7 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
BackgroundChange c = change;
c.m_def.m_sFile1 = bd.m_sFile1;
c.m_def.m_sFile2 = bd.m_sFile2;
c.m_fStartBeat = bpmseg.GetBeat();
c.m_fStartBeat = bpms[i]->GetBeat();
m_Layer[0].m_aBGChanges.push_back( c );
}
}
@@ -578,13 +583,15 @@ void BackgroundImpl::LoadFromSong( const Song* pSong )
else // pSong doesn't have an animation plan
{
Layer &layer = m_Layer[0];
float firstBeat = pSong->GetFirstBeat();
float lastBeat = pSong->GetLastBeat();
LoadFromRandom( pSong->m_fFirstBeat, pSong->m_fLastBeat, BackgroundChange() );
LoadFromRandom( firstBeat, lastBeat, BackgroundChange() );
// end showing the static song background
BackgroundChange change;
change.m_def = m_StaticBackgroundDef;
change.m_fStartBeat = pSong->m_fLastBeat;
change.m_fStartBeat = lastBeat;
layer.m_aBGChanges.push_back( change );
}
@@ -643,7 +650,7 @@ void BackgroundImpl::LoadFromSong( const Song* pSong )
continue;
float fStartBeat = change.m_fStartBeat;
float fEndBeat = pSong->m_fLastBeat;
float fEndBeat = pSong->GetLastBeat();
if( i+1 < mainlayer.m_aBGChanges.size() )
fEndBeat = mainlayer.m_aBGChanges[i+1].m_fStartBeat;
+19
View File
@@ -68,6 +68,25 @@ RString BackgroundChange::GetTextDescription() const
return s;
}
RString BackgroundChange::ToString() const
{
/* TODO: Technically we need to double-escape the filename
* (because it might contain '=') and then unescape the value
* returned by the MsdFile. */
return ssprintf("%.3f=%s=%.3f=%d=%d=%d=%s=%s=%s=%s=%s",
this->m_fStartBeat,
SmEscape(this->m_def.m_sFile1).c_str(),
this->m_fRate,
this->m_sTransition == SBT_CrossFade, // backward compat
this->m_def.m_sEffect == SBE_StretchRewind, // backward compat
this->m_def.m_sEffect != SBE_StretchNoLoop, // backward compat
this->m_def.m_sEffect.c_str(),
this->m_def.m_sFile2.c_str(),
this->m_sTransition.c_str(),
SmEscape(RageColor::NormalizeColorString(this->m_def.m_sColor1)).c_str(),
SmEscape(RageColor::NormalizeColorString(this->m_def.m_sColor2)).c_str());
}
const RString BACKGROUND_EFFECTS_DIR = "BackgroundEffects/";
const RString BACKGROUND_TRANSITIONS_DIR = "BackgroundTransitions/";
+5
View File
@@ -64,6 +64,11 @@ struct BackgroundChange
RString m_sTransition;
RString GetTextDescription() const;
/**
* @brief Get the string representation of the change.
* @return the string representation. */
RString ToString() const;
};
/** @brief Shared background-related routines. */
namespace BackgroundUtil
+2 -2
View File
@@ -242,7 +242,7 @@ void BeginnerHelper::DrawPrimitives()
DISPLAY->SetLighting( true );
DISPLAY->SetLightDirectional(
0,
RageColor(0.5,0.5,0.5,1),
RageColor(0.5f,0.5f,0.5f,1),
RageColor(1,1,1,1),
RageColor(0,0,0,1),
RageVector3(0, 0, 1) );
@@ -271,7 +271,7 @@ void BeginnerHelper::DrawPrimitives()
DISPLAY->SetLighting( true );
DISPLAY->SetLightDirectional(
0,
RageColor(0.5,0.5,0.5,1),
RageColor(0.5f,0.5f,0.5f,1),
RageColor(1,1,1,1),
RageColor(0,0,0,1),
RageVector3(0, 0, 1) );
+2
View File
@@ -5,6 +5,8 @@
#include "RageTextureID.h"
#include "ActorUtil.h"
RString GetRandomFileInDir( RString sDir );
Character::Character(): m_sCharDir(""), m_sCharacterID(""),
m_sDisplayName(""), m_sCardPath(""), m_sIconPath(""),
m_bUsableInRave(false), m_iPreloadRefcount(0) {}
+9 -1
View File
@@ -89,7 +89,7 @@ Character* CharacterManager::GetDefaultCharacter()
}
/* We always have the default character. */
ASSERT(0);
FAIL_M("There must be a default character available!");
return NULL;
}
@@ -152,6 +152,13 @@ public:
LuaHelpers::CreateTableFromArray(vChars, L);
return 1;
}
static int GetCharacterCount(T* p, lua_State *L)
{
vector<Character*> chars;
p->GetCharacters(chars);
lua_pushnumber(L, chars.size());
return 1;
}
LunaCharacterManager()
{
@@ -159,6 +166,7 @@ public:
// sm-ssc adds:
ADD_METHOD( GetRandomCharacter );
ADD_METHOD( GetAllCharacters );
ADD_METHOD( GetCharacterCount );
}
};
+7 -2
View File
@@ -1,19 +1,24 @@
/* CharacterManager - Manage characters. */
#ifndef CHARACTER_MANAGER_H
#define CHARACTER_MANAGER_H
class Character;
struct lua_State;
/** @brief Manage all of the Characters. */
class CharacterManager
{
public:
/** @brief Set up the character manager. */
CharacterManager();
/** @brief Destroy the character manager. */
~CharacterManager();
void GetCharacters( vector<Character*> &vpCharactersOut );
/** @brief Get one installed character at random.
* @return The random character. */
Character* GetRandomCharacter();
/** @brief Get the character assigned as the default.
* @return The default character. */
Character* GetDefaultCharacter();
Character* GetCharacterFromID( RString sCharacterID );
+1 -1
View File
@@ -108,7 +108,7 @@ static void Version()
#endif // WIN32
}
void CommandLineActions::Handle(LoadingWindow* pLW)
void CommandLineActions::Handle()
{
CommandLineArgs args;
for(int i=0; i<g_argc; ++i)
+2 -3
View File
@@ -7,9 +7,8 @@ class LoadingWindow;
namespace CommandLineActions
{
/**
* @brief Perform a utility function, then exit.
* @param pLW the LoadingWindow that is presently not used? */
void Handle(LoadingWindow* pLW);
* @brief Perform a utility function, then exit. */
void Handle();
/** @brief The housing for the command line arguments. */
class CommandLineArgs
+2 -2
View File
@@ -42,9 +42,9 @@ void CourseContentsList::SetFromGameState()
{
RemoveAllChildren();
if( GAMESTATE->m_MasterPlayerNumber == PlayerNumber_Invalid )
if( GAMESTATE->GetMasterPlayerNumber() == PlayerNumber_Invalid )
return;
const Trail *pMasterTrail = GAMESTATE->m_pCurTrail[GAMESTATE->m_MasterPlayerNumber];
const Trail *pMasterTrail = GAMESTATE->m_pCurTrail[GAMESTATE->GetMasterPlayerNumber()];
if( pMasterTrail == NULL )
return;
unsigned uNumEntriesToShow = pMasterTrail->m_vEntries.size();
+23 -2
View File
@@ -163,18 +163,39 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
// infer entry::Type from the first param
// todo: make sure these aren't generating bogus entries due
// to a lack of songs. -aj
int iNumSongs = SONGMAN->GetNumSongs();
LOG->Trace("[CourseLoaderCRS] sParams[1] = %s",sParams[1].c_str());
// most played
if( sParams[1].Left(strlen("BEST")) == "BEST" )
{
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
if( iChooseIndex > iNumSongs )
{
// looking up a song that doesn't exist.
LOG->UserLog( "Course file", sPath, "is trying to load BEST%i with only %i songs installed. "
"This entry will be ignored.", iChooseIndex, iNumSongs);
out.m_bIncomplete = true;
continue; // skip this #SONG
}
new_entry.iChooseIndex = iChooseIndex;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_MostPlays;
}
// least played
else if( sParams[1].Left(strlen("WORST")) == "WORST" )
{
new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1;
int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1;
if( iChooseIndex > iNumSongs )
{
// looking up a song that doesn't exist.
LOG->UserLog( "Course file", sPath, "is trying to load WORST%i with only %i songs installed. "
"This entry will be ignored.", iChooseIndex, iNumSongs);
out.m_bIncomplete = true;
continue; // skip this #SONG
}
new_entry.iChooseIndex = iChooseIndex;
CLAMP( new_entry.iChooseIndex, 0, 500 );
new_entry.songSort = SongSort_FewestPlays;
}
+4
View File
@@ -11,6 +11,10 @@ class XNode;
class CourseEntry;
class Song;
bool CompareCoursePointersBySortValueAscending( const Course *pSong1, const Course *pSong2 );
bool CompareCoursePointersBySortValueDescending( const Course *pSong1, const Course *pSong2 );
bool CompareCoursePointersByTitle( const Course *pCourse1, const Course *pCourse2 );
/** @brief Utility functions that deal with Courses. */
namespace CourseUtil
{
+6 -4
View File
@@ -11,6 +11,8 @@
#include "PrefsManager.h"
#include "Model.h"
int Neg1OrPos1();
#define DC_X( choice ) THEME->GetMetricF("DancingCharacters",ssprintf("2DCharacterXP%d",choice+1))
#define DC_Y( choice ) THEME->GetMetricF("DancingCharacters",ssprintf("2DCharacterYP%d",choice+1))
@@ -164,7 +166,7 @@ void DancingCharacters::LoadNextSong()
m_fThisCameraEndBeat = 0;
ASSERT( GAMESTATE->m_pCurSong );
m_fThisCameraEndBeat = GAMESTATE->m_pCurSong->m_fFirstBeat;
m_fThisCameraEndBeat = GAMESTATE->m_pCurSong->GetFirstBeat();
FOREACH_PlayerNumber( p )
if( GAMESTATE->IsPlayerEnabled(p) )
@@ -210,9 +212,9 @@ void DancingCharacters::Update( float fDelta )
bWasGameplayStarting = bGameplayStarting;
static float fLastBeat = GAMESTATE->m_Position.m_fSongBeat;
float firstBeat = GAMESTATE->m_pCurSong->GetFirstBeat();
float fThisBeat = GAMESTATE->m_Position.m_fSongBeat;
if( fLastBeat < GAMESTATE->m_pCurSong->m_fFirstBeat &&
fThisBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat )
if( fLastBeat < firstBeat && fThisBeat >= firstBeat )
{
FOREACH_PlayerNumber( p )
m_pCharacter[p]->PlayAnimation( "dance" );
@@ -344,7 +346,7 @@ void DancingCharacters::DrawPrimitives()
ambient,
diffuse,
specular,
RageVector3(-3, -7.5, +9) );
RageVector3(-3, -7.5f, +9) );
if( PREFSMAN->m_bCelShadeModels )
{
+2
View File
@@ -4,6 +4,8 @@
#include "EnumHelper.h"
#include <ctime>
int StringToDayInYear( RString sDayInYear );
/** @brief The number of days we check for previously. */
const int NUM_LAST_DAYS = 7;
/** @brief The number of weeks we check for previously. */
+20 -35
View File
@@ -67,49 +67,34 @@ RString GetCustomDifficulty( StepsType st, Difficulty dc, CourseType ct )
return DifficultyToString( dc );
}
const StepsTypeInfo &sti = GAMEMAN->GetStepsTypeInfo( st );
switch( sti.m_StepsTypeCategory )
if( dc == Difficulty_Edit )
{
DEFAULT_FAIL(sti.m_StepsTypeCategory);
case StepsTypeCategory_Single:
case StepsTypeCategory_Double:
if( dc == Difficulty_Edit )
return "Edit";
}
// OPTIMIZATION OPPORTUNITY: cache these metrics and cache the splitting
vector<RString> vsNames;
split( NAMES, ",", vsNames );
FOREACH( RString, vsNames, sName )
{
ThemeMetric<StepsType> STEPS_TYPE("CustomDifficulty",(*sName)+"StepsType");
if( STEPS_TYPE == StepsType_Invalid || st == STEPS_TYPE ) // match
{
return "Edit";
}
else
{
// OPTIMIZATION OPPORTUNITY: cache these metrics and cache the splitting
vector<RString> vsNames;
split( NAMES, ",", vsNames );
FOREACH( RString, vsNames, sName )
ThemeMetric<Difficulty> DIFFICULTY("CustomDifficulty",(*sName)+"Difficulty");
if( DIFFICULTY == Difficulty_Invalid || dc == DIFFICULTY ) // match
{
ThemeMetric<StepsType> STEPS_TYPE("CustomDifficulty",(*sName)+"StepsType");
if( STEPS_TYPE == StepsType_Invalid || st == STEPS_TYPE ) // match
ThemeMetric<CourseType> COURSE_TYPE("CustomDifficulty",(*sName)+"CourseType");
if( COURSE_TYPE == CourseType_Invalid || ct == COURSE_TYPE ) // match
{
ThemeMetric<Difficulty> DIFFICULTY("CustomDifficulty",(*sName)+"Difficulty");
if( DIFFICULTY == Difficulty_Invalid || dc == DIFFICULTY ) // match
{
ThemeMetric<CourseType> COURSE_TYPE("CustomDifficulty",(*sName)+"CourseType");
if( COURSE_TYPE == CourseType_Invalid || ct == COURSE_TYPE ) // match
{
ThemeMetric<RString> STRING("CustomDifficulty",(*sName)+"String");
return STRING.GetValue();
}
}
ThemeMetric<RString> STRING("CustomDifficulty",(*sName)+"String");
return STRING.GetValue();
}
}
// no matching CustomDifficulty, so use a regular difficulty name
if( dc == Difficulty_Invalid )
return RString();
return DifficultyToString( dc );
}
case StepsTypeCategory_Couple:
return "Couple";
case StepsTypeCategory_Routine:
return "Routine";
}
// no matching CustomDifficulty, so use a regular difficulty name
if( dc == Difficulty_Invalid )
return RString();
return DifficultyToString( dc );
}
LuaFunction( GetCustomDifficulty, GetCustomDifficulty(Enum::Check<StepsType>(L,1), Enum::Check<Difficulty>(L, 2), Enum::Check<CourseType>(L, 3, true)) );
+4 -12
View File
@@ -11,18 +11,10 @@
#include "SongUtil.h"
#include "XmlFile.h"
// MAX_METERS was previously set to NUM_Difficulty + MAX_EDITS_PER_SONG.
// This was all fine and well until AutoSetStyle was created. In certain
// gametypes (technomotion for example), if autogen is on, the number of
// available stepcharts exceeds MAX_METERS, resulting in a crash.
// My first thought to fix this was:
// (NUM_Difficulty (6) * NUM_StepsType (32)) + MAX_EDITS_PER_SONG (5*profileSlot (2)) = 202
// However, 202 rows may be a bit overkill.
// Dance has 6 stepstypes counting 3panel, Pump and Techno have 5.
// (6 difficulties * 6 stepstypes) + MAX_EDITS_PER_SONG (which is 5 * profileSlots) = 46 rows
// 46 seems to be a good enough number for this. If we get a crash again, up the
// "magic" 6. -aj
#define MAX_METERS (NUM_Difficulty * 6) + MAX_EDITS_PER_SONG
/** @brief Specifies the max number of charts available for a song.
*
* This includes autogenned charts. */
#define MAX_METERS (NUM_Difficulty * NUM_StepsType) + MAX_EDITS_PER_SONG
REGISTER_ACTOR_CLASS( StepsDisplayList );
+1 -1
View File
@@ -60,7 +60,7 @@ void EditMenu::GetSongsToShowForGroup( const RString &sGroup, vector<Song*> &vpS
for( int i=vpSongsOut.size()-1; i>=0; i-- )
{
const Song* pSong = vpSongsOut[i];
if( !pSong->NormallyDisplayed() || pSong->IsTutorial() || SONGMAN->WasLoadedFromAdditionalSongs(pSong) )
if( !pSong->NormallyDisplayed() || pSong->IsTutorial() )
vpSongsOut.erase( vpSongsOut.begin()+i );
}
break;
+102 -13
View File
@@ -10,6 +10,7 @@
#include "EnumHelper.h"
#include "ThemeMetric.h"
/** @brief What type of row is needed for the EditMenu? */
enum EditMenuRow
{
ROW_GROUP,
@@ -19,11 +20,19 @@ enum EditMenuRow
ROW_SOURCE_STEPS_TYPE,
ROW_SOURCE_STEPS,
ROW_ACTION,
NUM_EditMenuRow
NUM_EditMenuRow /**< The number of EditMenuRows available. */
};
/** @brief Loop through each EditMenuRow. */
#define FOREACH_EditMenuRow( r ) FOREACH_ENUM( EditMenuRow, r )
/**
* @brief Turn the EditMenuRow into a string.
* @param r the row.
* @return the string. */
const RString& EditMenuRowToString( EditMenuRow r );
/**
* @brief Turn the EditMenuRow into a localized string.
* @param r the row.
* @return the localized string. */
const RString& EditMenuRowToLocalizedString( EditMenuRow r );
/** @brief The different actions one can take on a step. */
@@ -38,9 +47,18 @@ enum EditMenuAction
};
/** @brief Loop through each EditMenuAction. */
#define FOREACH_EditMenuAction( ema ) FOREACH_ENUM( EditMenuAction, ema )
/**
* @brief Turn the EditMenuAction into a string.
* @param ema the action.
* @return the string. */
const RString& EditMenuActionToString( EditMenuAction ema );
/**
* @brief Turn the EditMenuAction into a localized string.
* @param ema the action.
* @return the localized string. */
const RString& EditMenuActionToLocalizedString( EditMenuAction ema );
/** @brief How many arrows are used for the EditMenu? */
const int NUM_ARROWS = 2;
/**
@@ -50,39 +68,110 @@ const int NUM_ARROWS = 2;
class EditMenu: public ActorFrame
{
public:
/** @brief Set up the EditMenu. */
EditMenu();
/** @brief Destroy the EditMenu. */
~EditMenu();
void Load( const RString &sType );
/** @brief Determine if we can move up.
* @return true if we can, false otherwise. */
bool CanGoUp();
/** @brief Determine if we can move down.
* @return true if we can, false otherwise. */
bool CanGoDown();
/** @brief Determine if we can move left.
* @return true if we can, false otherwise. */
bool CanGoLeft();
/** @brief Determine if we can move right.
* @return true if we can, false otherwise. */
bool CanGoRight();
/** @brief Determine if the EditMenuRow is selectable.
* @param row the row in question.
* @return true if it can be selected, false otherwise. */
bool RowIsSelectable( EditMenuRow row );
/** @brief Move up to the next selection. */
void Up();
/** @brief Move down to the next selection. */
void Down();
/** @brief Move left to the next selection. */
void Left();
/** @brief Move right to the next selection. */
void Right();
void RefreshAll();
RString GetSelectedGroup() const
/** @brief Retrieve the currently selected group.
* @return the current group. */
RString GetSelectedGroup() const
{
if( !SHOW_GROUPS.GetValue() ) return GROUP_ALL;
ASSERT_M((int)m_iSelection[ROW_GROUP] < (int)m_sGroups.size(),
ssprintf("Group selection %d < Number of groups %d", m_iSelection[ROW_GROUP], (int)m_sGroups.size()));
int groups = static_cast<int>(m_sGroups.size());
ASSERT_M(m_iSelection[ROW_GROUP] < groups,
ssprintf("Group selection %d < Number of groups %d",
m_iSelection[ROW_GROUP],
groups));
return m_sGroups[m_iSelection[ROW_GROUP]];
}
Song* GetSelectedSong() const { ASSERT(m_iSelection[ROW_SONG] < (int)m_pSongs.size()); return m_pSongs[m_iSelection[ROW_SONG]]; }
StepsType GetSelectedStepsType() const { ASSERT(m_iSelection[ROW_STEPS_TYPE] < (int)m_StepsTypes.size()); return m_StepsTypes[m_iSelection[ROW_STEPS_TYPE]]; }
Steps* GetSelectedSteps() const { ASSERT(m_iSelection[ROW_STEPS] < (int)m_vpSteps.size()); return m_vpSteps[m_iSelection[ROW_STEPS]].pSteps; }
Difficulty GetSelectedDifficulty() const { ASSERT(m_iSelection[ROW_STEPS] < (int)m_vpSteps.size()); return m_vpSteps[m_iSelection[ROW_STEPS]].dc; }
StepsType GetSelectedSourceStepsType() const { ASSERT(m_iSelection[ROW_SOURCE_STEPS_TYPE] < (int)m_StepsTypes.size()); return m_StepsTypes[m_iSelection[ROW_SOURCE_STEPS_TYPE]]; }
Steps* GetSelectedSourceSteps() const { ASSERT(m_iSelection[ROW_SOURCE_STEPS] < (int)m_vpSourceSteps.size()); return m_vpSourceSteps[m_iSelection[ROW_SOURCE_STEPS]].pSteps; }
Difficulty GetSelectedSourceDifficulty() const { ASSERT(m_iSelection[ROW_SOURCE_STEPS] < (int)m_vpSourceSteps.size()); return m_vpSourceSteps[m_iSelection[ROW_SOURCE_STEPS]].dc; }
EditMenuAction GetSelectedAction() const { ASSERT(m_iSelection[ROW_ACTION] < (int)m_Actions.size()); return m_Actions[m_iSelection[ROW_ACTION]]; }
/** @brief Retrieve the currently selected song.
* @return the current song. */
Song* GetSelectedSong() const
{
ASSERT(m_iSelection[ROW_SONG] < (int)m_pSongs.size());
return m_pSongs[m_iSelection[ROW_SONG]];
}
/** @brief Retrieve the currently selected steps type.
* @return the current steps type. */
StepsType GetSelectedStepsType() const
{
ASSERT(m_iSelection[ROW_STEPS_TYPE] < (int)m_StepsTypes.size());
return m_StepsTypes[m_iSelection[ROW_STEPS_TYPE]];
}
/** @brief Retrieve the currently selected steps.
* @return the current steps. */
Steps* GetSelectedSteps() const
{
ASSERT(m_iSelection[ROW_STEPS] < (int)m_vpSteps.size());
return m_vpSteps[m_iSelection[ROW_STEPS]].pSteps;
}
/** @brief Retrieve the currently selected difficulty.
* @return the current difficulty. */
Difficulty GetSelectedDifficulty() const
{
ASSERT(m_iSelection[ROW_STEPS] < (int)m_vpSteps.size());
return m_vpSteps[m_iSelection[ROW_STEPS]].dc;
}
/** @brief Retrieve the currently selected source steps type.
* @return the current source steps type. */
StepsType GetSelectedSourceStepsType() const
{
ASSERT(m_iSelection[ROW_SOURCE_STEPS_TYPE] < (int)m_StepsTypes.size());
return m_StepsTypes[m_iSelection[ROW_SOURCE_STEPS_TYPE]];
}
/** @brief Retrieve the currently selected source steps.
* @return the current source steps. */
Steps* GetSelectedSourceSteps() const
{
ASSERT(m_iSelection[ROW_SOURCE_STEPS] < (int)m_vpSourceSteps.size());
return m_vpSourceSteps[m_iSelection[ROW_SOURCE_STEPS]].pSteps;
}
/** @brief Retrieve the currently selected difficulty.
* @return the current difficulty. */
Difficulty GetSelectedSourceDifficulty() const
{
ASSERT(m_iSelection[ROW_SOURCE_STEPS] < (int)m_vpSourceSteps.size());
return m_vpSourceSteps[m_iSelection[ROW_SOURCE_STEPS]].dc;
}
/** @brief Retrieve the currently selected action.
* @return the current action. */
EditMenuAction GetSelectedAction() const
{
ASSERT(m_iSelection[ROW_ACTION] < (int)m_Actions.size());
return m_Actions[m_iSelection[ROW_ACTION]];
}
/** @brief Retrieve the currently selected row.
* @return the current row. */
EditMenuRow GetSelectedRow() const { return m_SelectedRow; }
private:
+62 -29
View File
@@ -15,7 +15,12 @@ extern "C"
/** @brief A general foreach loop for enumerators. */
#define FOREACH_ENUM( e, var ) for( e var=(e)0; var<NUM_##e; enum_add<e>( var, +1 ) )
int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const char *szType, bool bAllowInvalid );
int CheckEnum(lua_State *L,
LuaReference &table,
int iPos,
int iInvalid,
const char *szType,
bool bAllowInvalid);
template<typename T>
struct EnumTraits
@@ -33,7 +38,12 @@ namespace Enum
template<typename T>
static T Check( lua_State *L, int iPos, bool bAllowInvalid = false )
{
return (T) CheckEnum( L, EnumTraits<T>::StringToEnum, iPos, EnumTraits<T>::Invalid, EnumTraits<T>::szName, bAllowInvalid );
return (T) CheckEnum(L,
EnumTraits<T>::StringToEnum,
iPos,
EnumTraits<T>::Invalid,
EnumTraits<T>::szName,
bAllowInvalid);
}
template<typename T>
static void Push( lua_State *L, T iVal )
@@ -57,37 +67,47 @@ namespace Enum
const RString &EnumToString( int iVal, int iMax, const char **szNameArray, auto_ptr<RString> *pNameCache ); // XToString helper
#define XToString(X) \
COMPILE_ASSERT( NUM_##X == ARRAYLEN(X##Names) ); \
const RString& X##ToString( X x ) \
{ \
static auto_ptr<RString> as_##X##Name[NUM_##X+2]; \
return EnumToString( x, NUM_##X, X##Names, as_##X##Name ); \
} \
namespace StringConversion { template<> RString ToString<X>( const X &value ) { return X##ToString(value); } }
const RString& X##ToString(X x); \
COMPILE_ASSERT( NUM_##X == ARRAYLEN(X##Names) ); \
const RString& X##ToString( X x ) \
{ \
static auto_ptr<RString> as_##X##Name[NUM_##X+2]; \
return EnumToString( x, NUM_##X, X##Names, as_##X##Name ); \
} \
namespace StringConversion { template<> RString ToString<X>( const X &value ) { return X##ToString(value); } }
#define XToLocalizedString(X) \
const RString &X##ToLocalizedString( X x ) \
{ \
static auto_ptr<LocalizedString> g_##X##Name[NUM_##X]; \
if( g_##X##Name[0].get() == NULL ) { \
for( unsigned i = 0; i < NUM_##X; ++i ) \
{ \
auto_ptr<LocalizedString> ap( new LocalizedString(#X, X##ToString((X)i)) ); \
g_##X##Name[i] = ap; \
} \
const RString &X##ToLocalizedString(X x); \
const RString &X##ToLocalizedString( X x ) \
{ \
static auto_ptr<LocalizedString> g_##X##Name[NUM_##X]; \
if( g_##X##Name[0].get() == NULL ) { \
for( unsigned i = 0; i < NUM_##X; ++i ) \
{ \
auto_ptr<LocalizedString> ap( new LocalizedString(#X, X##ToString((X)i)) ); \
g_##X##Name[i] = ap; \
} \
return g_##X##Name[x]->GetValue(); \
}
} \
return g_##X##Name[x]->GetValue(); \
}
#define StringToX(X) \
X StringTo##X( const RString& s ) \
{ \
for( unsigned i = 0; i < ARRAYLEN(X##Names); ++i ) \
if( !s.CompareNoCase(X##Names[i]) ) \
return (X)i; \
return X##_Invalid; \
X StringTo##X(const RString&); \
X StringTo##X( const RString& s ) \
{ \
for( unsigned i = 0; i < ARRAYLEN(X##Names); ++i ) \
if( !s.CompareNoCase(X##Names[i]) ) \
return (X)i; \
return X##_Invalid; \
} \
namespace StringConversion \
{ \
template<> bool FromString<X>( const RString &sValue, X &out ) \
{ \
out = StringTo##X(sValue); \
return out != X##_Invalid; \
} \
namespace StringConversion { template<> bool FromString<X>( const RString &sValue, X &out ) { out = StringTo##X(sValue); return out != X##_Invalid; } }
}
// currently unused
#define LuaDeclareType(X)
@@ -123,8 +143,21 @@ static void Lua##X(lua_State* L) \
REGISTER_WITH_LUA_FUNCTION( Lua##X ); \
template<> X EnumTraits<X>::Invalid = X##_Invalid; \
template<> const char *EnumTraits<X>::szName = #X; \
namespace LuaHelpers { template<> bool FromStack<X>( lua_State *L, X &Object, int iOffset ) { Object = Enum::Check<X>( L, iOffset, true ); return Object != EnumTraits<X>::Invalid; } } \
namespace LuaHelpers { template<> void Push<X>( lua_State *L, const X &Object ) { Enum::Push<X>( L, Object ); } }
namespace LuaHelpers \
{ \
template<> bool FromStack<X>( lua_State *L, X &Object, int iOffset ) \
{ \
Object = Enum::Check<X>( L, iOffset, true ); \
return Object != EnumTraits<X>::Invalid; \
} \
} \
namespace LuaHelpers \
{ \
template<> void Push<X>( lua_State *L, const X &Object ) \
{ \
Enum::Push<X>( L, Object ); \
} \
}
#endif
+3
View File
@@ -19,6 +19,9 @@ class Style;
class Game;
struct lua_State;
int GetNumCreditsPaid();
int GetCreditsRequiredToPlayStyle( const Style *style );
class GameCommand
{
public:
+7 -1
View File
@@ -11,6 +11,7 @@
#include "PlayerNumber.h"
#include <float.h>
RString StepsTypeToString( StepsType st );
static vector<RString> GenerateRankingToFillInMarker()
{
@@ -359,12 +360,17 @@ float DisplayBpms::GetMin() const
}
float DisplayBpms::GetMax() const
{
return this->GetMaxWithin();
}
float DisplayBpms::GetMaxWithin(float highest) const
{
float fMax = 0;
FOREACH_CONST( float, vfBpms, f )
{
if( *f != -1 )
fMax = max( fMax, *f );
fMax = clamp(max( fMax, *f ), 0, highest);
}
return fMax;
}
+8
View File
@@ -4,6 +4,7 @@
#define GAME_CONSTANTS_AND_TYPES_H
#include "EnumHelper.h"
#include <float.h> // need the max for default.
// Note definitions
/** @brief Define the mininum difficulty value allowed. */
@@ -513,6 +514,13 @@ struct DisplayBpms
* @return the maximum BPM.
*/
float GetMax() const;
/**
* @brief Retrieve the maximum BPM of the set,
* but no higher than a certain value.
* @param highest the highest BPM to use.
* @return the maximum BPM.
*/
float GetMaxWithin(float highest = FLT_MAX) const;
/**
* @brief Determine if the BPM is really constant.
* @return Whether the BPM is constant or not.
+60 -10
View File
@@ -251,7 +251,7 @@ static const Style g_Style_Dance_Double =
static const Style g_Style_Dance_Couple =
{ // STYLE_DANCE_COUPLE
true, // m_bUsedForGameplay
true, // m_bUsedForEdit
false, // m_bUsedForEdit
false, // m_bUsedForDemonstration
false, // m_bUsedForHowToPlay
"couple", // m_szName
@@ -324,6 +324,50 @@ static const Style g_Style_Dance_Solo =
false, // m_bLockDifficulties
};
static const Style g_Style_Dance_Couple_Edit =
{ // STYLE_DANCE_COUPLE
false, // m_bUsedForGameplay
true, // m_bUsedForEdit
false, // m_bUsedForDemonstration
false, // m_bUsedForHowToPlay
"couple-edit", // m_szName
StepsType_dance_couple, // m_StepsType
StyleType_OnePlayerTwoSides, // m_StyleType
8, // m_iColsPerPlayer
{ // m_ColumnInfo[NUM_PLAYERS][MAX_COLS_PER_PLAYER];
{ // PLAYER_1
{ TRACK_1, -DANCE_COL_SPACING*4.f, NULL },
{ TRACK_2, -DANCE_COL_SPACING*3.f, NULL },
{ TRACK_3, -DANCE_COL_SPACING*2.f, NULL },
{ TRACK_4, -DANCE_COL_SPACING*1.f, NULL },
{ TRACK_5, +DANCE_COL_SPACING*1.f, NULL },
{ TRACK_6, +DANCE_COL_SPACING*2.f, NULL },
{ TRACK_7, +DANCE_COL_SPACING*3.f, NULL },
{ TRACK_8, +DANCE_COL_SPACING*4.f, NULL },
},
{ // PLAYER_2
{ TRACK_1, -DANCE_COL_SPACING*4.f, NULL },
{ TRACK_2, -DANCE_COL_SPACING*3.f, NULL },
{ TRACK_3, -DANCE_COL_SPACING*2.f, NULL },
{ TRACK_4, -DANCE_COL_SPACING*1.f, NULL },
{ TRACK_5, +DANCE_COL_SPACING*1.f, NULL },
{ TRACK_6, +DANCE_COL_SPACING*2.f, NULL },
{ TRACK_7, +DANCE_COL_SPACING*3.f, NULL },
{ TRACK_8, +DANCE_COL_SPACING*4.f, NULL },
},
},
{ // m_iInputColumn[NUM_GameController][NUM_GameButton]
{ 0, 3, 2, 1, Style::END_MAPPING },
{ 4, 7, 6, 5, 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_Dance_ThreePanel =
{ // STYLE_DANCE_THREEPANEL
true, // m_bUsedForGameplay
@@ -449,6 +493,7 @@ static const Style *g_apGame_Dance_Styles[] =
&g_Style_Dance_Double,
&g_Style_Dance_Couple,
&g_Style_Dance_Solo,
&g_Style_Dance_Couple_Edit,
&g_Style_Dance_Routine,
&g_Style_Dance_ThreePanel,
NULL
@@ -725,7 +770,7 @@ static const Style g_Style_Pump_Couple_Edit =
false, // m_bUsedForHowToPlay
"couple-edit", // m_szName
StepsType_pump_couple, // m_StepsType
StyleType_OnePlayerOneSide, // m_StyleType
StyleType_OnePlayerTwoSides, // m_StyleType
10, // m_iColsPerPlayer
{ // m_ColumnInfo[NUM_PLAYERS][MAX_COLS_PER_PLAYER];
{ // PLAYER_1
@@ -741,19 +786,24 @@ static const Style g_Style_Pump_Couple_Edit =
{ TRACK_10, +PUMP_COL_SPACING*5.0f+4, NULL },
},
{ // PLAYER_2
{ TRACK_1, -PUMP_COL_SPACING*2.0f, NULL },
{ TRACK_2, -PUMP_COL_SPACING*1.0f, NULL },
{ TRACK_3, +PUMP_COL_SPACING*0.0f, NULL },
{ TRACK_4, +PUMP_COL_SPACING*1.0f, NULL },
{ TRACK_5, +PUMP_COL_SPACING*2.0f, NULL },
{ TRACK_1, -PUMP_COL_SPACING*5.0f-4, NULL },
{ TRACK_2, -PUMP_COL_SPACING*4.0f-4, NULL },
{ TRACK_3, -PUMP_COL_SPACING*3.0f-4, NULL },
{ TRACK_4, -PUMP_COL_SPACING*2.0f-4, NULL },
{ TRACK_5, -PUMP_COL_SPACING*1.0f-4, NULL },
{ TRACK_6, +PUMP_COL_SPACING*1.0f+4, NULL },
{ TRACK_7, +PUMP_COL_SPACING*2.0f+4, NULL },
{ TRACK_8, +PUMP_COL_SPACING*3.0f+4, NULL },
{ TRACK_9, +PUMP_COL_SPACING*4.0f+4, NULL },
{ TRACK_10, +PUMP_COL_SPACING*5.0f+4, NULL },
},
},
{ // m_iInputColumn[NUM_GameController][NUM_GameButton]
{ 1, 3, 2, 0, 4, 6, 8, 7, 5, 9, Style::END_MAPPING },
{ 1, 3, 2, 0, 4, 6, 8, 7, 5, 9, Style::END_MAPPING },
{ 1, 3, 2, 0, 4, Style::END_MAPPING },
{ 6, 8, 7, 5, 9, Style::END_MAPPING },
},
{ // m_iColumnDrawOrder[MAX_COLS_PER_PLAYER];
2,1,3,0,4
2,1,3,0,4, 2+5,1+5,3+5,0+5,4+5
},
false, // m_bNeedsZoomOutWith2Players
false, // m_bCanUseBeginnerHelper
+8 -6
View File
@@ -60,8 +60,8 @@ struct MusicPlaying
RageSound *m_Music;
MusicPlaying( RageSound *Music )
{
m_Timing.AddBPMSegment( BPMSegment(0,120) );
m_NewTiming.AddBPMSegment( BPMSegment(0,120) );
m_Timing.AddSegment( SEGMENT_BPM, new BPMSegment(0,120) );
m_NewTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0,120) );
m_bHasTiming = false;
m_bTimingDelayed = false;
m_bApplyMusicRate = false;
@@ -142,8 +142,10 @@ static void StartMusic( MusicToPlay &ToPlay )
{
LOG->Trace( "Found '%s'", ToPlay.m_sTimingFile.c_str() );
Song song;
if( GetExtension(ToPlay.m_sTimingFile) == ".ssc" &&
SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) )
SSCLoader loaderSSC;
SMLoader loaderSM;
if(GetExtension(ToPlay.m_sTimingFile) == ".ssc" &&
loaderSSC.LoadFromSimfile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_SongTiming;
@@ -152,8 +154,8 @@ static void StartMusic( MusicToPlay &ToPlay )
if( pStepsCabinetLights )
pStepsCabinetLights->GetNoteData( ToPlay.m_LightsData );
}
else if( GetExtension(ToPlay.m_sTimingFile) == ".sm" &&
SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) )
else if(GetExtension(ToPlay.m_sTimingFile) == ".sm" &&
loaderSM.LoadFromSimfile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_SongTiming;
+3
View File
@@ -6,6 +6,9 @@
class TimingData;
class RageSound;
struct lua_State;
int MusicThread_start( void *p );
/** @brief High-level sound utilities. */
class GameSoundManager
{
+52 -25
View File
@@ -106,6 +106,7 @@ static Preference<Premium> g_Premium( "Premium", Premium_Off );
Preference<bool> GameState::m_bAutoJoin( "AutoJoin", false );
GameState::GameState() :
processedTiming( NULL ),
m_pCurGame( Message_CurrentGameChanged ),
m_pCurStyle( Message_CurrentStyleChanged ),
m_PlayMode( Message_PlayModeChanged ),
@@ -122,14 +123,14 @@ GameState::GameState() :
m_pCurTrail( Message_CurrentTrailP1Changed ),
m_bGameplayLeadIn( Message_GameplayLeadInChanged ),
m_bDidModeChangeNoteSkin( false ),
m_bIsUsingStepTiming( true ),
m_bInStepEditor( false ),
m_stEdit( Message_EditStepsTypeChanged ),
m_cdEdit( Message_EditCourseDifficultyChanged ),
m_pEditSourceSteps( Message_EditSourceStepsChanged ),
m_stEditSource( Message_EditSourceStepsTypeChanged ),
m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ),
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ),
m_bIsUsingStepTiming( true ),
m_bInStepEditor( false )
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged )
{
g_pImpl = new GameStateImpl;
@@ -188,6 +189,27 @@ GameState::~GameState()
SAFE_DELETE( m_Environment );
SAFE_DELETE( g_pImpl );
SAFE_DELETE( processedTiming );
}
PlayerNumber GameState::GetMasterPlayerNumber() const
{
return this->masterPlayerNumber;
}
void GameState::SetMasterPlayerNumber(const PlayerNumber p)
{
this->masterPlayerNumber = p;
}
TimingData * GameState::GetProcessedTimingData() const
{
return this->processedTiming;
}
void GameState::SetProcessedTimingData(TimingData * t)
{
this->processedTiming = t;
}
void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn )
@@ -244,7 +266,7 @@ void GameState::ResetPlayer( PlayerNumber pn )
void GameState::Reset()
{
m_MasterPlayerNumber = PLAYER_INVALID; // must initialize for UnjoinPlayer
this->SetMasterPlayerNumber(PLAYER_INVALID); // must initialize for UnjoinPlayer
FOREACH_PlayerNumber( pn )
UnjoinPlayer( pn );
@@ -334,14 +356,14 @@ void GameState::JoinPlayer( PlayerNumber pn )
* give the new player the same number of stage tokens that the old player
* has. */
if( GetCoinMode() == CoinMode_Pay && GetPremium() == Premium_2PlayersFor1Credit && GetNumSidesJoined() == 1 )
m_iPlayerStageTokens[pn] = m_iPlayerStageTokens[m_MasterPlayerNumber];
m_iPlayerStageTokens[pn] = m_iPlayerStageTokens[this->GetMasterPlayerNumber()];
else
m_iPlayerStageTokens[pn] = PREFSMAN->m_iSongsPerPlay;
m_bSideIsJoined[pn] = true;
if( m_MasterPlayerNumber == PLAYER_INVALID )
m_MasterPlayerNumber = pn;
if( this->GetMasterPlayerNumber() == PLAYER_INVALID )
this->SetMasterPlayerNumber(pn);
// if first player to join, set start time
if( GetNumSidesJoined() == 1 )
@@ -385,8 +407,8 @@ void GameState::UnjoinPlayer( PlayerNumber pn )
ResetPlayer( pn );
if( m_MasterPlayerNumber == pn )
m_MasterPlayerNumber = GetFirstHumanPlayer();
if( this->GetMasterPlayerNumber() == pn )
this->SetMasterPlayerNumber(GetFirstHumanPlayer());
/* Unjoin STATSMAN first, so steps used by this player are released
* and can be released by PROFILEMAN. */
@@ -398,7 +420,7 @@ void GameState::UnjoinPlayer( PlayerNumber pn )
MESSAGEMAN->Broadcast( msg );
// If there are no players left, reset some non-player-specific stuff, too.
if( m_MasterPlayerNumber == PLAYER_INVALID )
if( this->GetMasterPlayerNumber() == PLAYER_INVALID )
{
SongOptions so;
GetDefaultSongOptions( so );
@@ -635,8 +657,8 @@ int GameState::GetNumStagesForCurrentSongAndStepsOrCourse() const
if( pStyle == NULL )
{
const Steps *pSteps = NULL;
if( m_MasterPlayerNumber != PlayerNumber_Invalid )
pSteps = m_pCurSteps[m_MasterPlayerNumber];
if( this->GetMasterPlayerNumber() != PlayerNumber_Invalid )
pSteps = m_pCurSteps[this->GetMasterPlayerNumber()];
// Don't call GetFirstCompatibleStyle if numSidesJoined == 0.
// This happens because on SContinue when players are unjoined,
// pCurSteps will still be set while no players are joined. -Chris
@@ -690,7 +712,11 @@ void GameState::BeginStage()
if( !ARE_STAGE_PLAYER_MODS_FORCED )
{
FOREACH_PlayerNumber( p )
m_pPlayerState[p]->m_PlayerOptions.Assign( ModsLevel_Stage, m_pPlayerState[p]->m_PlayerOptions.GetPreferred() );
{
ModsGroup<PlayerOptions> &po = m_pPlayerState[p]->m_PlayerOptions;
po.Assign(ModsLevel_Stage,
m_pPlayerState[p]->m_PlayerOptions.GetPreferred());
}
}
if( !ARE_STAGE_SONG_MODS_FORCED )
m_SongOptions.Assign( ModsLevel_Stage, m_SongOptions.GetPreferred() );
@@ -969,7 +995,8 @@ update player position code goes here
float GameState::GetSongPercent( float beat ) const
{
// 0 = first step; 1 = last step
return (beat - m_pCurSong->m_fFirstBeat) / m_pCurSong->m_fLastBeat;
float curTime = this->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat(beat);
return (curTime - m_pCurSong->GetFirstSecond()) / m_pCurSong->GetLastSecond();
}
int GameState::GetNumStagesLeft( PlayerNumber pn ) const
@@ -994,9 +1021,9 @@ bool GameState::IsFinalStageForAnyHumanPlayer() const
bool GameState::IsAnExtraStage() const
{
if( m_MasterPlayerNumber == PlayerNumber_Invalid )
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
return false;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[m_MasterPlayerNumber] > 0;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] > 0;
}
static ThemeMetric<bool> LOCK_EXTRA_STAGE_SELECTION("GameState","LockExtraStageSelection");
@@ -1007,16 +1034,16 @@ bool GameState::IsAnExtraStageAndSelectionLocked() const
bool GameState::IsExtraStage() const
{
if( m_MasterPlayerNumber == PlayerNumber_Invalid )
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
return false;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[m_MasterPlayerNumber] == 1;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 1;
}
bool GameState::IsExtraStage2() const
{
if( m_MasterPlayerNumber == PlayerNumber_Invalid )
if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid )
return false;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[m_MasterPlayerNumber] == 2;
return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 2;
}
Stage GameState::GetCurrentStage() const
@@ -1059,7 +1086,7 @@ int GameState::GetCourseSongIndex() const
}
else
{
return STATSMAN->m_CurStageStats.m_player[m_MasterPlayerNumber].m_iSongsPlayed-1;
return STATSMAN->m_CurStageStats.m_player[this->GetMasterPlayerNumber()].m_iSongsPlayed-1;
}
}
@@ -1131,7 +1158,7 @@ void GameState::SetCurrentStyle( const Style *pStyle )
if( INPUTMAPPER )
{
if( GetCurrentStyle() && GetCurrentStyle()->m_StyleType == StyleType_OnePlayerTwoSides )
INPUTMAPPER->SetJoinControllers( m_MasterPlayerNumber );
INPUTMAPPER->SetJoinControllers( this->GetMasterPlayerNumber() );
else
INPUTMAPPER->SetJoinControllers( PLAYER_INVALID );
}
@@ -1192,7 +1219,7 @@ bool GameState::IsHumanPlayer( PlayerNumber pn ) const
return true;
case StyleType_OnePlayerOneSide:
case StyleType_OnePlayerTwoSides:
return pn == m_MasterPlayerNumber;
return pn == this->GetMasterPlayerNumber();
default:
ASSERT(0); // invalid style type
return false;
@@ -1276,7 +1303,7 @@ EarnedExtraStage GameState::CalculateEarnedExtraStage() const
if( GetSmallestNumStagesLeftForAnyHumanPlayer() > 0 )
return EarnedExtraStage_No;
if( m_iAwardedExtraStages[m_MasterPlayerNumber] >= 2 )
if( m_iAwardedExtraStages[this->GetMasterPlayerNumber()] >= 2 )
return EarnedExtraStage_No;
FOREACH_EnabledPlayer( pn )
@@ -2124,7 +2151,7 @@ public:
DEFINE_METHOD( IsPlayerEnabled, IsPlayerEnabled(Enum::Check<PlayerNumber>(L, 1)) )
DEFINE_METHOD( IsHumanPlayer, IsHumanPlayer(Enum::Check<PlayerNumber>(L, 1)) )
DEFINE_METHOD( GetPlayerDisplayName, GetPlayerDisplayName(Enum::Check<PlayerNumber>(L, 1)) )
DEFINE_METHOD( GetMasterPlayerNumber, m_MasterPlayerNumber )
DEFINE_METHOD( GetMasterPlayerNumber, GetMasterPlayerNumber() )
DEFINE_METHOD( GetMultiplayer, m_bMultiplayer )
static int SetMultiplayer( T* p, lua_State *L )
{
+26 -2
View File
@@ -32,9 +32,15 @@ class Style;
class TimingData;
class Trail;
SortOrder GetDefaultSort();
/** @brief Holds game data that is not saved between sessions. */
class GameState
{
/** @brief The player number used with Styles where one player controls both sides. */
PlayerNumber masterPlayerNumber;
/** @brief The TimingData that is used for processing certain functions. */
TimingData * processedTiming;
public:
/** @brief Set up the GameState with initial values. */
GameState();
@@ -91,8 +97,6 @@ public:
* to get one credit, only to have to put in another four coins to get
* the three credits needed to begin the game. */
BroadcastOnChange<int> m_iCoins;
/** @brief The player number used with Styles where one player controls both sides. */
PlayerNumber m_MasterPlayerNumber;
bool m_bMultiplayer;
int m_iNumMultiplayerNoteFields;
bool DifficultiesLocked() const;
@@ -139,6 +143,26 @@ public:
PlayerNumber GetFirstDisabledPlayer() const;
bool IsCpuPlayer( PlayerNumber pn ) const;
bool AnyPlayersAreCpu() const;
/**
* @brief Retrieve the present master player number.
* @return The master player number. */
PlayerNumber GetMasterPlayerNumber() const;
/**
* @brief Set the master player number.
* @param p the master player number. */
void SetMasterPlayerNumber(const PlayerNumber p);
/**
* @brief Retrieve the present timing data being processed.
* @return the timing data pointer. */
TimingData * GetProcessedTimingData() const;
/**
* @brief Set the timing data to be used with processing.
* @param t the timing data. */
void SetProcessedTimingData(TimingData * t);
bool IsCourseMode() const;
bool IsBattleMode() const; // not Rave
+1 -1
View File
@@ -47,7 +47,7 @@ void GameplayAssist::PlayTicks( const NoteData &nd, const PlayerState *ps )
if( nd.IsThereATapOrHoldHeadAtRow( r ) )
iClapRow = r;
if( iClapRow != -1 && !timing.IsWarpAtRow( iClapRow ) && !timing.IsFakeAtRow( iClapRow ) )
if( iClapRow != -1 && timing.IsJudgableAtRow(iClapRow))
{
const float fTickBeat = NoteRowToBeat( iClapRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
+2
View File
@@ -88,7 +88,9 @@ void GhostArrowRow::Update( float fDeltaTime )
void GhostArrowRow::DrawPrimitives()
{
for( unsigned c=0; c<m_Ghost.size(); c++ )
{
m_Ghost[c]->Draw();
}
}
void GhostArrowRow::DidTapNote( int iCol, TapNoteScore tns, bool bBright )
+31
View File
@@ -0,0 +1,31 @@
#include "global.h"
#include "InGameLoadingWindow.h"
#include "ScreenManager.h"
#include "ThemeManager.h"
#include "ActorUtil.h"
//REGISTER_ACTOR_CLASS( InGameLoadingWindow );
InGameLoadingWindow::InGameLoadingWindow() {
SetName("InGameLoadingWindow");
m_Text.SetName("LoadingText");
m_Text.LoadFromFont( THEME->GetPathF(m_sName, "LoadingText") );
m_Text.SetXY(0,0);
AddChild(&m_Text);
}
InGameLoadingWindow::~InGameLoadingWindow() {
RemoveChild(&m_Text);
}
void InGameLoadingWindow::SetText( RString str ) {
textChanged=true;
currentText=str;
}
void InGameLoadingWindow::Update(float delta) {
if(textChanged) {
m_Text.SetText( currentText );
textChanged=false;
}
}
+21
View File
@@ -0,0 +1,21 @@
#include "arch/LoadingWindow/LoadingWindow.h"
#include "BitmapText.h"
#include "RageTimer.h"
#include "global.h"
#include "ActorFrame.h"
class InGameLoadingWindow: public LoadingWindow, public ActorFrame {
public:
InGameLoadingWindow();
~InGameLoadingWindow();
void SetText( RString str );
void Update ( float delta );
private:
bool textChanged;
RString currentText;
BitmapText m_Text;
};
+1 -1
View File
@@ -64,7 +64,7 @@ static const AutoMappings g_DefaultKeyMappings = AutoMappings(
AutoMappingEntry( 0, KEY_KP_C2, GAME_BUTTON_MENUDOWN, true ),
AutoMappingEntry( 0, KEY_KP_ENTER, GAME_BUTTON_START, true ),
AutoMappingEntry( 0, KEY_KP_C0, GAME_BUTTON_SELECT, true ),
AutoMappingEntry( 0, KEY_NUMLOCK, GAME_BUTTON_BACK, true ),
AutoMappingEntry( 0, KEY_HYPHEN, GAME_BUTTON_BACK, true ), // laptop keyboards.
AutoMappingEntry( 0, KEY_F1, GAME_BUTTON_COIN, false ),
AutoMappingEntry( 0, KEY_SCRLLOCK, GAME_BUTTON_OPERATOR, false )
);
+3 -2
View File
@@ -11,6 +11,7 @@
#include "ThemeMetric.h"
#include "PlayerState.h"
void ReloadItems();
#define NUM_ITEM_TYPES THEME->GetMetricF("Inventory","NumItemTypes")
#define ITEM_DURATION_SECONDS THEME->GetMetricF("Inventory","ItemDurationSeconds")
@@ -118,10 +119,10 @@ void Inventory::Update( float fDelta )
}
}
Song &song = *GAMESTATE->m_pCurSong;
// use items if this player is CPU-controlled
if( m_pPlayerState->m_PlayerController != PC_HUMAN &&
GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
GAMESTATE->m_Position.m_fSongBeat < song.GetLastBeat() )
{
// every 1 seconds, try to use an item
int iLastSecond = (int)(RageTimer::GetTimeSinceStartFast() - fDelta);
+1 -2
View File
@@ -14,8 +14,7 @@ bool JsonUtil::LoadFromString(Json::Value &root, RString sData, RString &sErrorO
if (!parsingSuccessful)
{
RString err = reader.getFormatedErrorMessages();
sErrorOut = ssprintf("JSON: LoadFromFileShowErrors failed: %s", err.c_str());
LOG->Warn(sErrorOut);
LOG->Warn("JSON: LoadFromFileShowErrors failed: %s", err.c_str());
return false;
}
return true;
+31
View File
@@ -31,6 +31,24 @@ namespace JsonUtil
for(unsigned i=0; i<v.size(); i++)
fn(*v[i], root[i]);
}
template<class T>
static void SerializeVectorPointers(const vector<T*> &v, void fn(const T &, Json::Value &), Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize(v.size());
for(unsigned i=0; i<v.size(); i++)
fn(*v[i], root[i]);
}
template<class T>
static void SerializeVectorPointers(const vector<const T*> &v, void fn(const T *, Json::Value &), Json::Value &root)
{
root = Json::Value(Json::arrayValue);
root.resize(v.size());
for(unsigned i=0; i<v.size(); i++)
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)
@@ -149,6 +167,19 @@ namespace JsonUtil
fn(*v[i], root[i]);
}
}
template<class T>
static void DeserializeVectorPointers(vector<T*> &v, void fn(T *, const Json::Value &), const Json::Value &root)
{
for(unsigned i=0; i<v.size(); i++)
SAFE_DELETE(v[i]);
v.resize(root.size());
for(unsigned i=0; i<v.size(); i++)
{
v[i] = new T;
fn(*v[i], root[i]);
}
}
template<class T>
static void DeserializeArrayValues(vector<T> &v, const Json::Value &root)
+1 -1
View File
@@ -172,7 +172,7 @@ void LifeMeterBar::ChangeLife( HoldNoteScore score, TapNoteScore tscore )
switch( score )
{
case HNS_Held: fDeltaLife = +0; break;
case HNS_LetGo: fDeltaLife = -1.0; break;
case HNS_LetGo: fDeltaLife = -1.0f; break;
default:
ASSERT(0);
}
+69 -44
View File
@@ -26,6 +26,14 @@ void LifeMeterBattery::Load( const PlayerState *pPlayerState, PlayerStageStats *
const RString sType = "LifeMeterBattery";
PlayerNumber pn = pPlayerState->m_PlayerNumber;
MIN_SCORE_TO_KEEP_LIFE.Load(sType, "MinScoreToKeepLife");
DANGER_THRESHOLD.Load(sType, "DangerThreshold");
SUBTRACT_LIVES.Load(sType, "SubtractLives");
MINES_SUBTRACT_LIVES.Load(sType, "MinesSubtractLives");
HELD_ADD_LIVES.Load(sType, "HeldAddLives");
LET_GO_SUBTRACT_LIVES.Load(sType, "LetGoSubtractLives");
LIVES_FORMAT.Load(sType, "NumLivesFormat");
BATTERY_BLINK_TIME.Load(sType, "BatteryBlinkTime"); // 1.2f by default
bool bPlayerEnabled = GAMESTATE->IsPlayerEnabled( pPlayerState );
@@ -101,42 +109,54 @@ void LifeMeterBattery::OnSongEnded()
Refresh();
}
void LifeMeterBattery::SubtractLives( int iLives )
{
if( iLives <= 0 )
return;
m_iTrailingLivesLeft = m_iLivesLeft;
m_iLivesLeft -= iLives;
m_soundLoseLife.Play();
m_textNumLives.PlayCommand("LoseLife");
Refresh();
m_fBatteryBlinkTime = BATTERY_BLINK_TIME;
}
void LifeMeterBattery::AddLives( int iLives )
{
if( iLives <= 0 )
return;
m_iTrailingLivesLeft = m_iLivesLeft;
m_iLivesLeft += iLives;
m_soundGainLife.Play();
m_textNumLives.PlayCommand("GainLife");
Refresh();
m_fBatteryBlinkTime = 0;
}
void LifeMeterBattery::ChangeLives(int iLifeDiff)
{
if( iLifeDiff < 0 )
SubtractLives( abs(iLifeDiff) );
else if( iLifeDiff > 0 )
AddLives(iLifeDiff);
}
void LifeMeterBattery::ChangeLife( TapNoteScore score )
{
if( m_iLivesLeft == 0 )
return;
// todo: let the themer decide how this is handled. -aj
switch( score )
// this probably doesn't handle hold checkpoints. -aj
if( score == TNS_HitMine && MINES_SUBTRACT_LIVES > 0 )
SubtractLives(MINES_SUBTRACT_LIVES);
else
{
case TNS_W1:
case TNS_W2:
case TNS_W3:
break;
case TNS_W4:
case TNS_W5:
case TNS_Miss:
case TNS_HitMine:
m_iTrailingLivesLeft = m_iLivesLeft;
m_iLivesLeft--;
m_soundLoseLife.Play();
m_textNumLives.PlayCommand("LoseLife");
/*
m_textNumLives.SetZoom( 1.5f );
m_textNumLives.BeginTweening( 0.15f );
m_textNumLives.SetZoom( 1.0f );
*/
Refresh();
m_fBatteryBlinkTime = BATTERY_BLINK_TIME;
break;
default:
break;
/*
// xxx: this doesn't handle hold checkpoints.
ASSERT(0);
*/
if( score < MIN_SCORE_TO_KEEP_LIFE && score > TNS_CheckpointMiss && SUBTRACT_LIVES > 0 )
SubtractLives(SUBTRACT_LIVES);
}
Message msg( "LifeChanged" );
@@ -148,16 +168,19 @@ void LifeMeterBattery::ChangeLife( TapNoteScore score )
void LifeMeterBattery::ChangeLife( HoldNoteScore score, TapNoteScore tscore )
{
switch( score )
{
case HNS_Held:
break;
case HNS_LetGo:
ChangeLife( TNS_Miss ); // LetGo is the same as a miss
break;
default:
ASSERT(0);
}
if( m_iLivesLeft == 0 )
return;
if( score == HNS_Held && HELD_ADD_LIVES > 0 )
AddLives(HELD_ADD_LIVES);
if( score == HNS_LetGo && LET_GO_SUBTRACT_LIVES > 0 )
SubtractLives(LET_GO_SUBTRACT_LIVES);
Message msg( "LifeChanged" );
msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber );
msg.SetParam( "LifeMeter", LuaReference::CreateFromPush(*this) );
msg.SetParam( "LivesLeft", GetLivesLeft() );
MESSAGEMAN->Broadcast( msg );
}
void LifeMeterBattery::HandleTapScoreNone()
@@ -171,12 +194,12 @@ void LifeMeterBattery::ChangeLife( float fDeltaLifePercent )
bool LifeMeterBattery::IsInDanger() const
{
return false;
return m_iLivesLeft < DANGER_THRESHOLD;
}
bool LifeMeterBattery::IsHot() const
{
return false;
return m_iLivesLeft == GAMESTATE->m_SongOptions.GetSong().m_iBatteryLives;
}
bool LifeMeterBattery::IsFailing() const
@@ -208,7 +231,8 @@ void LifeMeterBattery::Refresh()
}
else
{
m_textNumLives.SetText( ssprintf("x%d", m_iLivesLeft-1) );
//m_textNumLives.SetText( ssprintf("x%d", m_iLivesLeft-1) );
m_textNumLives.SetText( ssprintf(LIVES_FORMAT.GetValue(), m_iLivesLeft-1) );
m_sprBattery.SetState( 3 );
}
}
@@ -244,13 +268,14 @@ class LunaLifeMeterBattery: public Luna<LifeMeterBattery>
{
public:
static int GetLivesLeft( T* p, lua_State *L ) { lua_pushnumber( L, p->GetLivesLeft() ); return 1; }
// is this right? wtf -q2x
static int GetTotalLives( T* p, lua_State *L ) { lua_pushnumber( L, GAMESTATE->m_SongOptions.GetSong().m_iBatteryLives ); return 1; }
static int ChangeLives( T* p, lua_State *L ) { p->ChangeLives(IArg(1)); return 0; }
LunaLifeMeterBattery()
{
ADD_METHOD( GetLivesLeft );
ADD_METHOD( GetTotalLives );
ADD_METHOD( ChangeLives );
}
};
+11 -1
View File
@@ -32,18 +32,28 @@ public:
void Refresh();
int GetLivesLeft() { return m_iLivesLeft; }
void ChangeLives(int iLifeDiff);
// Lua
virtual void PushSelf( lua_State *L );
private:
void SubtractLives( int iLives );
void AddLives( int iLives );
int m_iLivesLeft; // dead when 0
int m_iTrailingLivesLeft; // lags m_iLivesLeft
float m_fBatteryBlinkTime; // if > 0 battery is blinking
// theme metrics added for sm-ssc
ThemeMetric<float> BATTERY_BLINK_TIME;
ThemeMetric<TapNoteScore> MIN_SCORE_TO_KEEP_LIFE;
ThemeMetric<int> DANGER_THRESHOLD;
ThemeMetric<int> SUBTRACT_LIVES;
ThemeMetric<int> MINES_SUBTRACT_LIVES;
ThemeMetric<int> HELD_ADD_LIVES;
ThemeMetric<int> LET_GO_SUBTRACT_LIVES;
ThemeMetric<RString> LIVES_FORMAT;
AutoActor m_sprFrame;
Sprite m_sprBattery;
+2
View File
@@ -220,9 +220,11 @@ inline bool MyLua_checkintboolean( lua_State *L, int iArg )
#define FArg(n) ((float) luaL_checknumber(L,(n)))
#define LuaFunction( func, expr ) \
int LuaFunc_##func( lua_State *L ); \
int LuaFunc_##func( lua_State *L ) { \
LuaHelpers::Push( L, expr ); return 1; \
} \
void LuaFunc_Register_##func( lua_State *L ); \
void LuaFunc_Register_##func( lua_State *L ) { lua_register( L, #func, LuaFunc_##func ); } \
REGISTER_WITH_LUA_FUNCTION( LuaFunc_Register_##func );
+2 -2
View File
@@ -59,8 +59,8 @@ 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->m_SongTiming.GetElapsedTimeFromBeat( pSong->m_fLastBeat );
fEndTime = pSong->GetLastSecond();
const float fDistance = fEndTime - pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime;
const float fTweenBufferTime = IN_LENGTH.GetValue() + OUT_LENGTH.GetValue();
+2 -1
View File
@@ -107,7 +107,7 @@ ScreenSyncOverlay.cpp ScreenSyncOverlay.h \
ScreenSystemLayer.cpp ScreenSystemLayer.h ScreenSetTime.cpp ScreenSetTime.h \
ScreenSongOptions.cpp ScreenSongOptions.h \
ScreenSplash.cpp ScreenSplash.h \
ScreenTestFonts.cpp ScreenTestFonts.h ScreenTestInput.cpp ScreenTestInput.h \
ScreenTestInput.cpp ScreenTestInput.h \
ScreenTestLights.cpp ScreenTestLights.h ScreenTestSound.cpp ScreenTestSound.h ScreenTextEntry.cpp ScreenTextEntry.h \
ScreenTitleMenu.cpp ScreenTitleMenu.h \
ScreenUnlockBrowse.cpp ScreenUnlockBrowse.h \
@@ -340,6 +340,7 @@ DualScrollBar.cpp DualScrollBar.h \
EditMenu.cpp EditMenu.h FadingBanner.cpp FadingBanner.h \
GradeDisplay.cpp GradeDisplay.h GraphDisplay.cpp GraphDisplay.h \
GrooveRadar.cpp GrooveRadar.h HelpDisplay.cpp HelpDisplay.h \
InGameLoadingWindow.cpp InGameLoadingWindow.h \
MemoryCardDisplay.cpp MemoryCardDisplay.h \
MenuTimer.cpp MenuTimer.h \
ModIcon.cpp ModIcon.h ModIconRow.cpp ModIconRow.h \
+2
View File
@@ -9,6 +9,8 @@
#include "ThemeMetric.h"
#include "AutoActor.h"
RString WARNING_COMMAND_NAME( size_t i );
class MenuTimer : public ActorFrame
{
public:
+2 -2
View File
@@ -73,8 +73,8 @@ void SongMeterDisplay::Update( float fDeltaTime )
{
if( GAMESTATE->m_pCurSong )
{
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 fSongStartSeconds = GAMESTATE->m_pCurSong->GetFirstSecond();
float fSongEndSeconds = GAMESTATE->m_pCurSong->GetLastSecond();
float fPercentPositionSong = SCALE( GAMESTATE->m_Position.m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
CLAMP( fPercentPositionSong, 0, 1 );
+3 -24
View File
@@ -10,6 +10,8 @@
#include "LuaManager.h"
#include "Foreach.h"
int OptionToPreferredColumn( RString sOptionText );
REGISTER_ACTOR_CLASS( ModIconRow );
ModIconRow::ModIconRow()
@@ -66,35 +68,12 @@ void ModIconRow::HandleMessage( const Message &msg )
struct OptionColumnEntry
{
char *szString;
const 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[] =
{
+3 -3
View File
@@ -969,7 +969,7 @@ void MusicWheel::FilterWheelItemDatas(vector<MusicWheelItemData *> &aUnFilteredD
}
/* If the song has no steps for the current style, remove it. */
if( !pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) )
if( !CommonMetrics::AUTO_SET_STYLE && !pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) )
{
aiRemove[i] = true;
continue;
@@ -1476,11 +1476,11 @@ RString MusicWheel::JumpToPrevGroup()
// in case it wasn't found above:
for( unsigned int i = m_CurWheelItemData.size()-1; i > 0; --i )
{
LOG->Trace( ssprintf("JumpToPrevGroup iteration 2 | i = %u",i) );
LOG->Trace( "JumpToPrevGroup iteration 2 | i = %u",i );
if( m_CurWheelItemData[i]->m_Type == TYPE_SECTION )
{
m_iSelection = i;
LOG->Trace( ssprintf("finding it in #2 | i = %u | text = %s",i, m_CurWheelItemData[i]->m_sText.c_str()) );
LOG->Trace( "finding it in #2 | i = %u | text = %s",i, m_CurWheelItemData[i]->m_sText.c_str() );
return m_CurWheelItemData[i]->m_sText;
}
}
+4
View File
@@ -170,6 +170,10 @@ MusicWheelItem::MusicWheelItem( const MusicWheelItem &cpy ):
MusicWheelItem::~MusicWheelItem()
{
FOREACH_ENUM( MusicWheelItemType, i )
{
SAFE_DELETE(m_pText[i]);
}
delete m_pTextSectionCount;
}
+1 -1
View File
@@ -237,7 +237,7 @@ void NetworkSyncManager::ReportScore(int playerID, int step, int score, int comb
if( !useSMserver ) //Make sure that we are using the network
return;
LOG->Trace( ssprintf("Player ID %i combo = %i", playerID, combo) );
LOG->Trace( "Player ID %i combo = %i", playerID, combo );
m_packet.ClearPacket();
m_packet.Write1( NSCGSU );
+220 -11
View File
@@ -9,6 +9,7 @@
#include "RageUtil.h"
#include "RageLog.h"
#include "XmlFile.h"
#include "GameState.h" // blame radar calculations.
#include "Foreach.h"
#include "RageUtil_AutoPtr.h"
@@ -458,6 +459,31 @@ int NoteData::GetLastRow() const
return iOldestRowFoundSoFar;
}
bool NoteData::IsTap(const TapNote &tn, const int row) const
{
return (tn.type != TapNote::empty && tn.type != TapNote::mine
&& tn.type != TapNote::lift && tn.type != TapNote::fake
&& GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row));
}
bool NoteData::IsMine(const TapNote &tn, const int row) const
{
return (tn.type == TapNote::mine
&& GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row));
}
bool NoteData::IsLift(const TapNote &tn, const int row) const
{
return (tn.type == TapNote::lift
&& GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row));
}
bool NoteData::IsFake(const TapNote &tn, const int row) const
{
return (tn.type == TapNote::fake
|| !GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row));
}
int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const
{
int iNumNotes = 0;
@@ -465,9 +491,7 @@ 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::lift && tn.type != TapNote::fake )
if (this->IsTap(GetTapNote(t, r), r))
iNumNotes++;
}
}
@@ -485,7 +509,7 @@ int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const
{
int iNumNotes = 0;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex )
if( IsThereATapAtRow(r) )
if( IsThereATapAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) )
iNumNotes++;
return iNumNotes;
@@ -498,7 +522,7 @@ int NoteData::GetNumMines( int iStartIndex, int iEndIndex ) const
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
if( GetTapNote(t, r).type == TapNote::mine )
if (this->IsMine(GetTapNote(t, r), r))
iNumMines++;
}
@@ -509,7 +533,7 @@ int NoteData::GetNumRowsWithTapOrHoldHead( int iStartIndex, int iEndIndex ) cons
{
int iNumNotes = 0;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex )
if( IsThereATapOrHoldHeadAtRow(r) )
if( IsThereATapOrHoldHeadAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) )
iNumNotes++;
return iNumNotes;
@@ -560,7 +584,8 @@ int NoteData::GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, in
{
if( !RowNeedsAtLeastSimultaneousPresses(iMinSimultaneousPresses,r) )
continue;
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r))
continue;
iNum++;
}
@@ -576,7 +601,10 @@ int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int
for( int t=0; t<GetNumTracks(); t++ )
{
const TapNote &tn = GetTapNote(t, r);
if( tn.type != TapNote::mine && tn.type != TapNote::empty && tn.type != TapNote::fake ) // mines don't count
if( tn.type != TapNote::mine // mines don't count.
&& tn.type != TapNote::empty
&& tn.type != TapNote::fake
&& GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r))
iNumNotesThisIndex++;
}
if( iNumNotesThisIndex >= iMinTaps )
@@ -598,6 +626,8 @@ int NoteData::GetNumHoldNotes( int iStartIndex, int iEndIndex ) const
if( lBegin->second.type != TapNote::hold_head ||
lBegin->second.subType != TapNote::hold_head_hold )
continue;
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first))
continue;
iNumHolds++;
}
}
@@ -616,6 +646,8 @@ int NoteData::GetNumRolls( int iStartIndex, int iEndIndex ) const
if( lBegin->second.type != TapNote::hold_head ||
lBegin->second.subType != TapNote::hold_head_roll )
continue;
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first))
continue;
iNumRolls++;
}
}
@@ -629,7 +661,7 @@ int NoteData::GetNumLifts( int iStartIndex, int iEndIndex ) const
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
if( GetTapNote(t, r).type == TapNote::lift )
if( this->IsLift(GetTapNote(t, r), r))
iNumLifts++;
}
@@ -643,13 +675,190 @@ int NoteData::GetNumFakes( int iStartIndex, int iEndIndex ) const
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
if( GetTapNote(t, r).type == TapNote::fake )
iNumFakes++;
if( this->IsFake(GetTapNote(t, r), r))
iNumFakes++;
}
return iNumFakes;
}
bool NoteData::IsPlayer1(const int track, const TapNote &tn) const
{
if (this->IsComposite())
{
return tn.pn == PLAYER_1;
}
return track < (this->GetNumTracks() / 2);
}
pair<int, int> NoteData::GetNumTapNotesTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = GetTapNote(t, r);
if (this->IsTap(tn, r))
{
if (this->IsPlayer1(t, tn))
num.first++;
else
num.second++;
}
}
}
return num;
}
pair<int, int> NoteData::GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps,
int startRow,
int endRow) const
{
pair<int, int> num(0, 0);
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, startRow, endRow )
{
pair<int, int> found(0, 0);
for( int t=0; t<GetNumTracks(); t++ )
{
const TapNote &tn = GetTapNote(t, r);
if (this->IsTap(tn, r))
{
if (this->IsPlayer1(t, tn))
found.first++;
else
found.second++;
}
}
if (found.first >= minTaps)
num.first++;
if (found.second >= minTaps)
num.second++;
}
return num;
}
pair<int, int> NoteData::GetNumJumpsTwoPlayer( int iStartIndex, int iEndIndex ) const
{
return GetNumRowsWithSimultaneousTapsTwoPlayer( 2, iStartIndex, iEndIndex );
}
pair<int, int> NoteData::GetNumHandsTwoPlayer( int iStartIndex, int iEndIndex ) const
{
return GetNumRowsWithSimultaneousTapsTwoPlayer( 3, iStartIndex, iEndIndex );
}
pair<int, int> NoteData::GetNumQuadsTwoPlayer( int iStartIndex, int iEndIndex ) const
{
return GetNumRowsWithSimultaneousTapsTwoPlayer( 4, iStartIndex, iEndIndex );
}
pair<int, int> NoteData::GetNumHoldNotesTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); ++t )
{
NoteData::TrackMap::const_iterator lBegin, lEnd;
GetTapNoteRangeExclusive( t, iStartIndex, iEndIndex, lBegin, lEnd );
for( ; lBegin != lEnd; ++lBegin )
{
if( lBegin->second.type != TapNote::hold_head ||
lBegin->second.subType != TapNote::hold_head_hold )
continue;
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first))
continue;
if (this->IsPlayer1(t, lBegin->second))
num.first++;
else
num.second++;
}
}
return num;
}
pair<int, int> NoteData::GetNumMinesTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = GetTapNote(t, r);
if (this->IsMine(tn, r))
{
if (this->IsPlayer1(t, tn))
num.first++;
else
num.second++;
}
}
}
return num;
}
pair<int, int> NoteData::GetNumRollsTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); ++t )
{
NoteData::TrackMap::const_iterator lBegin, lEnd;
GetTapNoteRangeExclusive( t, iStartIndex, iEndIndex, lBegin, lEnd );
for( ; lBegin != lEnd; ++lBegin )
{
if( lBegin->second.type != TapNote::hold_head ||
lBegin->second.subType != TapNote::hold_head_roll )
continue;
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first))
continue;
if (this->IsPlayer1(t, lBegin->second))
num.first++;
else
num.second++;
}
}
return num;
}
pair<int, int> NoteData::GetNumLiftsTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = GetTapNote(t, r);
if (this->IsLift(tn, r))
{
if (this->IsPlayer1(t, tn))
num.first++;
else
num.second++;
}
}
}
return num;
}
pair<int, int> NoteData::GetNumFakesTwoPlayer( int iStartIndex, int iEndIndex ) const
{
pair<int, int> num(0, 0);
for( int t=0; t<GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = GetTapNote(t, r);
if (this->IsFake(tn, r))
{
if (this->IsPlayer1(t, tn))
num.first++;
else
num.second++;
}
}
}
return num;
}
/*
int NoteData::GetNumMinefields( int iStartIndex, int iEndIndex ) const
{
+105 -10
View File
@@ -90,7 +90,46 @@ private:
// There's no point in inserting empty notes into the map.
// Any blank space in the map is defined to be empty.
vector<TrackMap> m_TapNotes;
/**
* @brief Determine whether this note is for Player 1 or Player 2.
* @param track the track/column the note is in.
* @param tn the note in question. Required for routine mode.
* @return true if it's for player 1, false for player 2. */
bool IsPlayer1(const int track, const TapNote &tn) const;
/**
* @brief Determine if the note in questino should be counted as a tap.
* @param tn the note in question.
* @param row the row it lives in.
* @return true if it's a tap, false otherwise. */
bool IsTap(const TapNote &tn, const int row) const;
/**
* @brief Determine if the note in questino should be counted as a mine.
* @param tn the note in question.
* @param row the row it lives in.
* @return true if it's a mine, false otherwise. */
bool IsMine(const TapNote &tn, const int row) const;
/**
* @brief Determine if the note in questino should be counted as a lift.
* @param tn the note in question.
* @param row the row it lives in.
* @return true if it's a lift, false otherwise. */
bool IsLift(const TapNote &tn, const int row) const;
/**
* @brief Determine if the note in questino should be counted as a fake.
* @param tn the note in question.
* @param row the row it lives in.
* @return true if it's a fake, false otherwise. */
bool IsFake(const TapNote &tn, const int row) const;
pair<int, int> GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps = 2,
int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
public:
void Init();
@@ -130,7 +169,8 @@ public:
* @param iEndRow the ending point.
* @param begin the eventual beginning point of the range.
* @param end the eventual end point of the range. */
void GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const;
void GetTapNoteRange(int iTrack, int iStartRow, int iEndRow,
TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const;
/**
* @brief Return a constant iterator range for [rowBegin,rowEnd).
* @param iTrack the column to use.
@@ -158,13 +198,17 @@ public:
/* Return an iterator range include iStartRow to iEndRow. Extend the range to include
* hold notes overlapping the boundary. */
void GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end, bool bIncludeAdjacent=false ) const;
void GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &begin, TrackMap::iterator &end, bool bIncludeAdjacent=false );
void GetTapNoteRangeInclusive(int iTrack, int iStartRow, int iEndRow,
TrackMap::const_iterator &begin, TrackMap::const_iterator &end, bool bIncludeAdjacent=false ) const;
void GetTapNoteRangeInclusive(int iTrack, int iStartRow, int iEndRow,
TrackMap::iterator &begin, TrackMap::iterator &end, bool bIncludeAdjacent=false );
/* Return an iterator range include iStartRow to iEndRow. Shrink the range to exclude
* hold notes overlapping the boundary. */
void GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const;
void GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &begin, TrackMap::iterator &end );
void GetTapNoteRangeExclusive(int iTrack, int iStartRow, int iEndRow,
TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const;
void GetTapNoteRangeExclusive(int iTrack, int iStartRow, int iEndRow,
TrackMap::iterator &begin, TrackMap::iterator &end );
/* Returns the row of the first TapNote on the track that has a row greater than rowInOut. */
@@ -175,7 +219,17 @@ public:
void MoveTapNoteTrack( int dest, int src );
void SetTapNote( int track, int row, const TapNote& tn );
void AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ); // add note hold note merging overlapping HoldNotes and destroying TapNotes underneath
/**
* @brief Add a hold note, merging other overlapping holds and destroying
* tap notes underneath.
* @param iTrack the column to work with.
* @param iStartRow the starting row.
* @param iEndRow the ending row.
* @param tn the tap note. */
void AddHoldNote(int iTrack,
int iStartRow,
int iEndRow,
TapNote tn );
void ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack );
void ClearRange( int rowBegin, int rowEnd );
@@ -222,7 +276,12 @@ public:
// Count rows that contain iMinTaps or more taps.
int GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumJumps( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const { return GetNumRowsWithSimultaneousTaps( 2, iStartIndex, iEndIndex ); }
int GetNumJumps( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const
{
return GetNumRowsWithSimultaneousTaps( 2, iStartIndex, iEndIndex );
}
// This row needs at least iMinSimultaneousPresses either tapped or held.
bool RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, int row ) const;
@@ -230,15 +289,51 @@ public:
// Count rows that need iMinSimultaneousPresses either tapped or held.
int GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumHands( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const { return GetNumRowsWithSimultaneousPresses( 3, iStartIndex, iEndIndex ); }
int GetNumQuads( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const { return GetNumRowsWithSimultaneousPresses( 4, iStartIndex, iEndIndex ); }
int GetNumHands( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const
{
return GetNumRowsWithSimultaneousPresses( 3, iStartIndex, iEndIndex );
}
int GetNumQuads( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const
{
return GetNumRowsWithSimultaneousPresses( 4, iStartIndex, iEndIndex );
}
// and the other notetypes
int GetNumLifts( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumFakes( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
// the couple/routine style variants of the above.
pair<int, int> GetNumTapNotesTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumJumpsTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumHandsTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumQuadsTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumHoldNotesTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumMinesTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumRollsTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumLiftsTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
pair<int, int> GetNumFakesTwoPlayer(int startRow = 0,
int endRow = MAX_NOTE_ROW) const;
// Transformations
void LoadTransformed( const NoteData& original, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ); // -1 for iOriginalTracksToTakeFrom means no track
void LoadTransformed(const NoteData& original,
int iNewNumTracks,
const int iOriginalTrackToTakeFrom[] ); // -1 for iOriginalTracksToTakeFrom means no track
// XML
XNode* CreateNode() const;
+7 -8
View File
@@ -778,13 +778,6 @@ void NoteDataUtil::LoadTransformedLightsFromTwo( const NoteData &marquee, const
NoteDataUtil::RemoveMines( out );
}
struct RadarStats {
int taps;
int jumps;
int hands;
int quads;
};
RadarStats CalculateRadarStatsFast( const NoteData &in, RadarStats &out )
{
out.taps = 0;
@@ -799,12 +792,18 @@ RadarStats CalculateRadarStatsFast( const NoteData &in, RadarStats &out )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( in, t, r, 0, MAX_NOTE_ROW )
{
/* This function deals strictly with taps, jumps, hands, and quads.
* As such, all rows in here have to be judgable. */
if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r))
continue;
const TapNote &tn = in.GetTapNote(t, r);
switch( tn.type )
{
case TapNote::mine:
case TapNote::empty:
case TapNote::fake:
case TapNote::autoKeysound:
continue; // skip these types - they don't count
}
@@ -886,7 +885,7 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds,
case RadarCategory_Jumps: out[rc] = (float) stats.jumps; break;
case RadarCategory_Holds: out[rc] = (float) in.GetNumHoldNotes(); break;
case RadarCategory_Mines: out[rc] = (float) in.GetNumMines(); break;
case RadarCategory_Hands: out[rc] = (float) stats.hands; break;
case RadarCategory_Hands: out[rc] = (float) in.GetNumHands(); break;
case RadarCategory_Rolls: out[rc] = (float) in.GetNumRolls(); break;
case RadarCategory_Lifts: out[rc] = (float) in.GetNumLifts(); break;
case RadarCategory_Fakes: out[rc] = (float) in.GetNumFakes(); break;
+18
View File
@@ -10,6 +10,24 @@ class NoteData;
class Song;
struct AttackArray;
/** @brief A limited selection of the RadarValues. */
struct RadarStats
{
/** @brief The number of tap notes in the song. */
int taps;
/** @brief The number of jumps in the song. */
int jumps;
/** @brief The number of 3 panel hits in the song. */
int hands;
/** @brief The number of 4 panel hits in the song. */
int quads;
};
void PlaceAutoKeysound( NoteData &out, int row, TapNote akTap );
int FindLongestOverlappingHoldNoteForAnyTrack( const NoteData &in, int iRow );
void LightTransformHelper( const NoteData &in, NoteData &out, const vector<int> &aiTracks );
RadarStats CalculateRadarStatsFast( const NoteData &in, RadarStats &out );
/**
* @brief Utility functions that deal with NoteData.
*
+1 -1
View File
@@ -349,7 +349,7 @@ void NoteDataWithScoring::GetActualRadarValues( const NoteData &in, const Player
case RadarCategory_Hands: out[rc] = (float) GetSuccessfulHands( in ); break;
case RadarCategory_Rolls: out[rc] = (float) GetNumHoldNotesWithScore( in, TapNote::hold_head_roll, HNS_Held ); break;
case RadarCategory_Lifts: out[rc] = (float) GetSuccessfulLifts( in, TNS_W4 ); break;
case RadarCategory_Fakes: out[rc] = (float) in.GetNumLifts(); break;
case RadarCategory_Fakes: out[rc] = (float) in.GetNumFakes(); break;
//case RadarCategory_Minefields: out[rc] = (float) GetNumMinefieldsWithScore( in, TapNote::hold_head_mine, HNS_Held ); break;
DEFAULT_FAIL( rc );
}
+57 -5
View File
@@ -46,6 +46,8 @@ static const NoteType MAX_DISPLAY_NOTE_TYPE = (NoteType)7;
struct NoteMetricCache_t
{
bool m_bDrawHoldHeadForTapsOnSameRow;
bool m_bDrawRollHeadForTapsOnSameRow;
bool m_bTapHoldRollOnRowMeansHold;
float m_fAnimationLength[NUM_NotePart];
bool m_bAnimationIsVivid[NUM_NotePart];
RageVector2 m_fAdditionTextureCoordOffset[NUM_NotePart];
@@ -69,6 +71,8 @@ struct NoteMetricCache_t
void NoteMetricCache_t::Load( const RString &sButton )
{
m_bDrawHoldHeadForTapsOnSameRow = NOTESKIN->GetMetricB(sButton,"DrawHoldHeadForTapsOnSameRow");
m_bDrawRollHeadForTapsOnSameRow = NOTESKIN->GetMetricB(sButton,"DrawRollHeadForTapsOnSameRow");
m_bTapHoldRollOnRowMeansHold = NOTESKIN->GetMetricB(sButton,"TapHoldRollOnRowMeansHold");
FOREACH_NotePart( p )
{
const RString &s = NotePartToString(p);
@@ -271,6 +275,11 @@ bool NoteDisplay::DrawHoldHeadForTapsOnSameRow() const
return cache->m_bDrawHoldHeadForTapsOnSameRow;
}
bool NoteDisplay::DrawRollHeadForTapsOnSameRow() const
{
return cache->m_bDrawRollHeadForTapsOnSameRow;
}
void NoteDisplay::Update( float fDeltaTime )
{
/* This function is static: it's called once per game loop, not once per
@@ -683,11 +692,18 @@ void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, in
const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
const RageColor diffuse = RageColor(fColorScale,fColorScale,fColorScale,fAlpha);
const RageColor glow = RageColor(1,1,1,fGlow);
float fRotationX = 0, fRotationY = 0, fRotationZ = 0;
float fRotationX = 0, fRotationZ = 0;
const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset );
bool bIsHoldHead = tn.type == tn.hold_head;
bool bIsHoldCap = bIsHoldHead || tn.type == tn.hold_tail;
fRotationZ = ArrowEffects::GetRotationZ( m_pPlayerState, fBeat, bIsHoldHead );
if( !bIsHoldCap )
{
fRotationX = ArrowEffects::GetRotationX( m_pPlayerState, fYOffset );
}
fRotationX = ArrowEffects::GetRotationX( m_pPlayerState, fYOffset );
fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset );
fRotationZ = ArrowEffects::GetRotationZ( m_pPlayerState, fBeat, tn.type == tn.hold_head );
if( tn.type != tn.hold_head )
fColorScale *= ArrowEffects::GetBrightness( m_pPlayerState, fBeat );
@@ -718,7 +734,13 @@ void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, in
}
}
void NoteDisplay::DrawTap( const TapNote& tn, int iCol, float fBeat, bool bOnSameRowAsHoldStart, bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar )
void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat,
bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart,
bool bIsAddition, float fPercentFadeToFail,
float fReverseOffsetPixels,
float fDrawDistanceAfterTargetsPixels,
float fDrawDistanceBeforeTargetsPixels,
float fFadeInPercentOfDrawFar)
{
Actor* pActor = NULL;
NotePart part = NotePart_Tap;
@@ -738,10 +760,40 @@ void NoteDisplay::DrawTap( const TapNote& tn, int iCol, float fBeat, bool bOnSam
pActor = GetTapActor( m_TapFake, NotePart_Fake, fBeat );
part = NotePart_Fake;
}
// TODO: Simplify all of the below.
else if (bOnSameRowAsHoldStart && bOnSameRowAsRollStart)
{
if (cache->m_bDrawHoldHeadForTapsOnSameRow && cache->m_bDrawRollHeadForTapsOnSameRow)
{
if (cache->m_bTapHoldRollOnRowMeansHold) // another new metric?
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, false, false );
}
else
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, true, false );
}
}
else if (cache->m_bDrawHoldHeadForTapsOnSameRow)
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, false, false );
}
else if (cache->m_bDrawRollHeadForTapsOnSameRow)
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, true, false );
}
}
else if( bOnSameRowAsHoldStart && cache->m_bDrawHoldHeadForTapsOnSameRow )
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, false, false );
}
else if( bOnSameRowAsRollStart && cache->m_bDrawRollHeadForTapsOnSameRow )
{
pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, fBeat, true, false );
}
else
{
pActor = GetTapActor( m_TapNote, NotePart_Tap, fBeat );
+22 -2
View File
@@ -80,13 +80,33 @@ public:
static void Update( float fDeltaTime );
void DrawTap( const TapNote& tn, int iCol, float fBeat, bool bOnSameRowAsHoldStart, bool bIsAddition,
float fPercentFadeToFail, float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar );
/**
* @brief Draw the TapNote onto the NoteField.
* @param tn the TapNote in question.
* @param iCol the column.
* @param float fBeat the beat to draw them on.
* @param bOnSameRowAsHoldStart a flag to see if a hold is on the same beat.
* @param bOnSameRowAsRollStart a flag to see if a roll is on the same beat.
* @param bIsAddition a flag to see if this note was added via mods.
* @param fPercentFadeToFail at what point do the notes fade on failure?
* @param fReverseOffsetPixels How are the notes adjusted on Reverse?
* @param fDrawDistanceAfterTargetsPixels how much to draw after the receptors.
* @param fDrawDistanceBeforeTargetsPixels how much ot draw before the receptors.
* @param fFadeInPercentOfDrawFar when to start fading in. */
void DrawTap(const TapNote& tn, int iCol, float fBeat,
bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollBeat,
bool bIsAddition, float fPercentFadeToFail,
float fReverseOffsetPixels,
float fDrawDistanceAfterTargetsPixels,
float fDrawDistanceBeforeTargetsPixels,
float fFadeInPercentOfDrawFar );
void DrawHold( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result,
bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels,
float fDrawDistanceBeforeTargetsPixels2, float fFadeInPercentOfDrawFar );
bool DrawHoldHeadForTapsOnSameRow() const;
bool DrawRollHeadForTapsOnSameRow() const;
private:
void SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLength, bool bVivid );
+104 -54
View File
@@ -20,6 +20,9 @@
#include "Course.h"
#include "NoteData.h"
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels );
float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels );
static ThemeMetric<bool> SHOW_BOARD( "NoteField", "ShowBoard" );
static ThemeMetric<bool> SHOW_BEAT_BARS( "NoteField", "ShowBeatBars" );
static ThemeMetric<float> FADE_BEFORE_TARGETS_PERCENT( "NoteField", "FadeBeforeTargetsPercent" );
@@ -208,10 +211,10 @@ void NoteField::Load(
//int i1 = m_pNoteData->GetNumTracks();
//int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer;
ASSERT_M( m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(), GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer) );
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer));
// The NoteSkin may have changed at the beginning of a new course song.
RString sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin;
@@ -303,7 +306,7 @@ void NoteField::Update( float fDeltaTime )
// TODO: Remove use of PlayerNumber.
PlayerNumber pn = m_pPlayerState->m_PlayerNumber;
if( pn == GAMESTATE->m_MasterPlayerNumber )
if( pn == GAMESTATE->GetMasterPlayerNumber() )
NoteDisplay::Update( fDeltaTime );
}
@@ -574,7 +577,7 @@ void NoteField::DrawTickcountText( const float fBeat, int iTicks )
m_textMeasureNumber.Draw();
}
void NoteField::DrawComboText( const float fBeat, int iCombo )
void NoteField::DrawComboText( const float fBeat, int iCombo, int iMiss )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
@@ -586,7 +589,7 @@ void NoteField::DrawComboText( const float fBeat, int iCombo )
m_textMeasureNumber.SetHorizAlign( COMBO_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( COMBO_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%d", iCombo) );
m_textMeasureNumber.SetText( ssprintf("%d/%d", iCombo, iMiss) );
m_textMeasureNumber.SetXY( (COMBO_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
@@ -814,7 +817,7 @@ void NoteField::DrawPrimitives()
float fDrawScale = 1;
fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt );
fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_TINY] );
fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] );
iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale);
iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * fDrawScale);
@@ -846,27 +849,26 @@ void NoteField::DrawPrimitives()
}
const TimingData *pTiming = GetDisplayedTiming(m_pPlayerState);
const vector<TimingSegment *> *segs = pTiming->allTimingSegments;
unsigned i = 0;
// Draw beat bars
if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
{
const TimingData &timing = *pTiming;
const vector<TimeSignatureSegment> &vTimeSignatureSegments = timing.m_vTimeSignatureSegments;
const vector<TimingSegment *> &tSigs = segs[SEGMENT_TIME_SIG];
int iMeasureIndex = 0;
FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter )
for (i = 0; i < tSigs.size(); i++)
{
vector<TimeSignatureSegment>::const_iterator next = iter;
next++;
int iSegmentEndRow = (next == vTimeSignatureSegments.end()) ? iLastRowToDraw : next->GetRow();
TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]);
int iSegmentEndRow = (i + 1 == tSigs.size()) ? iLastRowToDraw : tSigs[i+1]->GetRow();
// beat bars every 16th note
int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)iter->GetDen()) / 4 ) / 4;
int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4;
// In 4/4, every 16th beat bar is a measure
int iMeasureBarFrequency = iter->GetNum() * 4;
int iMeasureBarFrequency = ts->GetNum() * 4;
int iBeatBarsDrawn = 0;
for( int i=iter->GetRow(); i < iSegmentEndRow; i += iDrawBeatBarsEveryRows )
for( int j=ts->GetRow(); j < iSegmentEndRow; j += iDrawBeatBarsEveryRows )
{
bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0;
BeatBarType type = quarter_beat;
@@ -876,7 +878,7 @@ void NoteField::DrawPrimitives()
type = beat;
else if( iBeatBarsDrawn % 2 == 0 )
type = half_beat;
float fBeat = NoteRowToBeat(i);
float fBeat = NoteRowToBeat(j);
if( IS_ON_SCREEN(fBeat) )
{
@@ -899,8 +901,9 @@ void NoteField::DrawPrimitives()
// Scroll text
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( ScrollSegment, timing.m_ScrollSegments, seg )
for (i = 0; i < segs[SEGMENT_SCROLL].size(); i++)
{
ScrollSegment *seg = static_cast<ScrollSegment *>(segs[SEGMENT_SCROLL][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -911,8 +914,9 @@ void NoteField::DrawPrimitives()
}
// BPM text
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, seg )
for (i = 0; i < segs[SEGMENT_BPM].size(); i++)
{
BPMSegment *seg = static_cast<BPMSegment *>(segs[SEGMENT_BPM][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -922,8 +926,9 @@ void NoteField::DrawPrimitives()
}
// Freeze text
FOREACH_CONST( StopSegment, timing.m_StopSegments, seg )
for (i = 0; i < segs[SEGMENT_STOP_DELAY].size(); i++)
{
StopSegment *seg = static_cast<StopSegment *>(segs[SEGMENT_STOP_DELAY][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -933,8 +938,9 @@ void NoteField::DrawPrimitives()
}
// Warp text
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, seg )
for (i = 0; i < segs[SEGMENT_WARP].size(); i++)
{
WarpSegment *seg = static_cast<WarpSegment *>(segs[SEGMENT_WARP][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -945,8 +951,9 @@ void NoteField::DrawPrimitives()
// Time Signature text
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, seg )
for (i = 0; i < segs[SEGMENT_TIME_SIG].size(); i++)
{
TimeSignatureSegment *seg = static_cast<TimeSignatureSegment *>(segs[SEGMENT_TIME_SIG][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -958,8 +965,9 @@ void NoteField::DrawPrimitives()
if( GAMESTATE->m_bIsUsingStepTiming )
{
// Tickcount text
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, seg )
for (i = 0; i < segs[SEGMENT_TICKCOUNT].size(); i++)
{
TickcountSegment *seg = static_cast<TickcountSegment *>(segs[SEGMENT_TICKCOUNT][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -972,20 +980,22 @@ void NoteField::DrawPrimitives()
if( GAMESTATE->m_bIsUsingStepTiming )
{
// Combo text
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, seg )
for (i = 0; i < segs[SEGMENT_COMBO].size(); i++)
{
ComboSegment *seg = static_cast<ComboSegment *>(segs[SEGMENT_COMBO][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
DrawComboText( fBeat, seg->GetCombo() );
DrawComboText( fBeat, seg->GetCombo(), seg->GetMissCombo() );
}
}
}
// Label text
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, seg )
for (i = 0; i < segs[SEGMENT_LABEL].size(); i++)
{
LabelSegment *seg = static_cast<LabelSegment *>(segs[SEGMENT_LABEL][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -996,8 +1006,10 @@ void NoteField::DrawPrimitives()
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, seg )
// Speed text
for (i = 0; i < segs[SEGMENT_SPEED].size(); i++)
{
SpeedSegment *seg = static_cast<SpeedSegment *>(segs[SEGMENT_SPEED][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -1008,11 +1020,12 @@ void NoteField::DrawPrimitives()
}
}
// Speed text
// Fake text
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, seg )
for (i = 0; i < segs[SEGMENT_FAKE].size(); i++)
{
FakeSegment *seg = static_cast<FakeSegment *>(segs[SEGMENT_FAKE][i]);
if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
{
float fBeat = seg->GetBeat();
@@ -1042,6 +1055,22 @@ void NoteField::DrawPrimitives()
}
}
}
else
{
AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks :
GAMESTATE->m_pCurSong->m_Attacks;
FOREACH_CONST(Attack, attacks, a)
{
float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond);
if (BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
BeatToNoteRow(fBeat) <= iLastRowToDraw &&
IS_ON_SCREEN(fBeat))
{
this->DrawAttackText(fBeat, *a);
}
}
}
if( !GAMESTATE->m_bIsUsingStepTiming )
{
@@ -1055,55 +1084,55 @@ void NoteField::DrawPrimitives()
case EditMode_Full:
{
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
FOREACH_BackgroundLayer( j )
iter[j] = GAMESTATE->m_pCurSong->GetBackgroundChanges(j).begin();
while( 1 )
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
FOREACH_BackgroundLayer( i )
FOREACH_BackgroundLayer( j )
{
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
if( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
float fBeat = iter[j]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
viLowestIndex.push_back( j );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
viLowestIndex.push_back( j );
}
}
if( viLowestIndex.empty() )
{
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
FOREACH_BackgroundLayer( j )
ASSERT( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() );
break;
}
if( IS_ON_SCREEN(fLowestBeat) )
{
vector<RString> vsBGChanges;
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
{
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
ASSERT( iter[*bl] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*bl).end() );
const BackgroundChange& change = *iter[*bl];
RString s = change.GetTextDescription();
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
if( *bl!=0 )
s = ssprintf("%d: ",*bl) + s;
vsBGChanges.push_back( s );
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
}
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
FOREACH_CONST( BackgroundLayer, viLowestIndex, bl )
iter[*bl]++;
}
}
break;
@@ -1143,11 +1172,13 @@ void NoteField::DrawPrimitives()
float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f );
const Style* pStyle = GAMESTATE->GetCurrentStyle();
ASSERT( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer == m_pNoteData->GetNumTracks() );
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer));
for( int i=0; i<m_pNoteData->GetNumTracks(); i++ ) // for each arrow column
for( int j=0; j<m_pNoteData->GetNumTracks(); j++ ) // for each arrow column
{
const int c = pStyle->m_iColumnDrawOrder[i];
const int c = pStyle->m_iColumnDrawOrder[j];
bool bAnyUpcomingInThisCol = false;
@@ -1158,7 +1189,7 @@ void NoteField::DrawPrimitives()
for( ; begin != end; ++begin )
{
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, i);
const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j);
if( tn.type != TapNote::hold_head )
continue; // skip
@@ -1257,13 +1288,31 @@ void NoteField::DrawPrimitives()
{
for( int c2=0; c2<m_pNoteData->GetNumTracks(); c2++ )
{
if( m_pNoteData->GetTapNote(c2, q).type == TapNote::hold_head)
const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
if(tmp.type == TapNote::hold_head &&
tmp.subType == TapNote::hold_head_hold)
{
bHoldNoteBeginsOnThisBeat = true;
break;
}
}
}
// do the same for a roll.
bool bRollNoteBeginsOnThisBeat = false;
if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() )
{
for( int c2=0; c2<m_pNoteData->GetNumTracks(); c2++ )
{
const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
if(tmp.type == TapNote::hold_head &&
tmp.subType == TapNote::hold_head_roll)
{
bRollNoteBeginsOnThisBeat = true;
break;
}
}
}
bool bIsInSelectionRange = false;
if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
@@ -1273,7 +1322,8 @@ 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, NoteRowToVisibleBeat(m_pPlayerState, q), bHoldNoteBeginsOnThisBeat,
displayCols->display[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q),
bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat,
bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
FADE_BEFORE_TARGETS_PERCENT );
+1 -1
View File
@@ -62,7 +62,7 @@ protected:
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 DrawComboText( const float fBeat, int iCombo, int iMiss );
void DrawLabelText( const float fBeat, RString sLabel );
void DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode );
void DrawScrollText( const float fBeat, float fPercent );
+1 -1
View File
@@ -200,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( sSkinName.EqualsNoCase(asSkinNames[i]) )
if( 0==stricmp(sSkinName, asSkinNames[i]) )
return true;
return false;
}
+18 -2
View File
@@ -164,7 +164,7 @@ struct TapNote
{
if (type_ > TapNote::fake )
{
LOG->Trace(ssprintf("Invalid tap note type %d (most likely) due to random vanish issues. Assume it doesn't need judging.", type_ ) );
LOG->Trace("Invalid tap note type %d (most likely) due to random vanish issues. Assume it doesn't need judging.", (int)type_ );
type = TapNote::empty;
}
}
@@ -334,7 +334,23 @@ static inline float ToBeat(int row) { return NoteRowToBeat(row); }
*/
static inline float ToBeat(float beat) { return beat; }
/**
* @brief Scales the position.
* @param T start - the starting row of the scaling region
* @param T length - the length of the scaling region
* @param T newLength - the new length of the scaling region
* @param T position - the position to scale
* @return T the scaled position
*/
template<typename T>
inline T ScalePosition( T start, T length, T newLength, T position )
{
if( position < start )
return position;
if( position >= start + length )
return position - length + newLength;
return start + (position - start) * newLength / length;
}
#endif
+10 -7
View File
@@ -32,20 +32,23 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set<RString> &Bl
vector<RString> list;
BlacklistedImages.clear();
SSCLoader::GetApplicableFiles( sPath, list );
SSCLoader loaderSSC;
loaderSSC.GetApplicableFiles( sPath, list );
if( !list.empty() )
{
if( !SSCLoader::LoadFromDir( sPath, out ) )
if( !loaderSSC.LoadFromDir( sPath, out ) )
return false;
SSCLoader::TidyUpData( out, false );
loaderSSC.TidyUpData( out, false );
return true;
}
SMALoader::GetApplicableFiles( sPath, list );
SMALoader loaderSMA;
loaderSMA.GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMALoader::LoadFromDir( sPath, out );
SMLoader::GetApplicableFiles( sPath, list );
return loaderSMA.LoadFromDir( sPath, out );
SMLoader loaderSM;
loaderSM.GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMLoader::LoadFromDir( sPath, out );
return loaderSM.LoadFromDir( sPath, out );
DWILoader::GetApplicableFiles( sPath, list );
if( !list.empty() )
return DWILoader::LoadFromDir( sPath, out, BlacklistedImages );
+102 -13
View File
@@ -459,13 +459,16 @@ static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map<
* 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) )
RString dir = out.GetSongDir();
if (dir.empty())
dir = Dirname(sPath);
if( !IsAFile(dir+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) )
if( IsAFile(dir+fn) )
{
nData = fn;
break;
@@ -473,9 +476,9 @@ static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map<
}
}
if( !IsAFile(out.GetSongDir()+nData) )
if( !IsAFile(dir+nData) )
{
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
LOG->UserLog( "Song file", dir, "references key \"%s\" that can't be found", nData.c_str() );
return false;
}
@@ -548,6 +551,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
{
map<RString, int> mapIdToKeysoundIndex;
map<int, float> mapNoteRowToBPM;
MeasureToTimeSig_t sigAdjustments;
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
@@ -572,8 +576,8 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.m_Timing.AddBPMSegment( newSeg );
BPMSegment * newSeg = new BPMSegment( 0, fBPM );
out.m_Timing.AddSegment(SEGMENT_BPM, newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
@@ -622,7 +626,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.m_Timing.SetBPMAtBeat( fBeat, (float) iVal );
mapNoteRowToBPM[ BeatToNoteRow(fBeat) ] = iVal;
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
@@ -639,7 +643,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.m_Timing.SetBPMAtBeat( fBeat, fBPM );
mapNoteRowToBPM[ BeatToNoteRow(fBeat) ] = fBPM;
}
else
{
@@ -662,9 +666,9 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
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.GetPause() );
StopSegment * newSeg = new StopSegment( fBeat, fFreezeSecs );
out.m_Timing.AddSegment( SEGMENT_STOP_DELAY, newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg->GetPause() );
}
else
{
@@ -676,6 +680,11 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
}
}
for( map<int, float>::iterator it = mapNoteRowToBPM.begin(); it != mapNoteRowToBPM.end(); it ++ )
{
out.m_Timing.SetBPMAtRow( it->first, it->second );
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, outSong, sigAdjustments );
@@ -690,6 +699,8 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
}
NameToData_t::const_iterator it;
bool hasBGM = false;
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
@@ -739,6 +750,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
{
if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
{
hasBGM = true;
// shift the auto keysound as far right as possible
int iLastEmptyTrack = -1;
if( ndNotes.GetTapLastEmptyTrack(row, iLastEmptyTrack) &&
@@ -766,6 +778,12 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
}
}
if (!hasBGM)
{
LOG->Warn("The song at %s is missing a #XXX01 tag! We're unable to load.", sPath.c_str());
return false;
}
/* Handles hold notes like uBMPlay.
* Different BMS simulators support hold notes differently.
* See http://nvyu.net/rdm/ex.php for more info.
@@ -958,7 +976,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
iTransformNewToOld[15] = BMS_P2_TURN;
break;
default:
ASSERT(0);
ASSERT_M(0, ssprintf("Invalid StepsType when parsing BMS file %s!", sPath.c_str()));
}
// shift all of the autokeysound tracks onto the main tracks
@@ -984,7 +1002,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
}
else
{
LOG->UserLog( "Song file", sPath, "has too much simultenous autokeysound tracks." );
LOG->UserLog( "Song file", sPath, "has too much simultaneous autokeysound tracks." );
}
}
}
@@ -1052,6 +1070,75 @@ void BMSLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.bml"), out );
}
bool BMSLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps & out )
{
Song dummy;
// TODO: Simplify this copy/paste from LoadFromDir.
vector<NameToData_t> BMSData;
BMSData.push_back(NameToData_t());
ReadBMSFile(cachePath, BMSData.back());
RString commonSubstring;
GetCommonTagFromMapList( BMSData, "#title", commonSubstring );
Steps *copy = dummy.CreateSteps();
copy->SetDifficulty( Difficulty_Medium );
RString sTag;
if( GetTagFromMap( BMSData[0], "#title", sTag ) && sTag.size() != commonSubstring.size() )
{
sTag = sTag.substr( commonSubstring.size(), sTag.size() - commonSubstring.size() );
sTag.MakeLower();
if( sTag.find('l') != sTag.npos )
{
unsigned lPos = sTag.find('l');
if( lPos > 2 && sTag.substr(lPos-2,4) == "solo" )
{
copy->SetDifficulty( Difficulty_Edit );
}
else
{
copy->SetDifficulty( Difficulty_Easy );
}
}
else if( sTag.find('a') != sTag.npos )
copy->SetDifficulty( Difficulty_Hard );
else if( sTag.find('b') != sTag.npos )
copy->SetDifficulty( Difficulty_Beginner );
}
if( commonSubstring == "" )
{
copy->SetDifficulty(Difficulty_Medium);
RString sTag;
if (GetTagFromMap(BMSData[0], "#title#", sTag))
SearchForDifficulty(sTag, copy);
}
ReadGlobalTags( BMSData[0], dummy );
if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' )
{
switch( commonSubstring[commonSubstring.size() - 1] )
{
case '[':
case '(':
case '<':
commonSubstring = commonSubstring.substr(0, commonSubstring.size() - 2);
default:
break;
}
}
map<RString, int> mapFilenameToKeysoundIndex;
const bool ok = LoadFromBMSFile( cachePath, BMSData[0], *copy, dummy, mapFilenameToKeysoundIndex );
if( ok )
{
out.SetNoteData(copy->GetNoteData());
}
return ok;
}
bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
{
LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );
@@ -1156,6 +1243,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
iMainDataIndex = i;
ReadGlobalTags( aBMSData[iMainDataIndex], out );
out.m_sSongFileName = out.GetSongDir() + arrayBMSFileNames[iMainDataIndex];
// The brackets before the difficulty are in common substring, so remove them if it's found.
if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' )
@@ -1188,6 +1276,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
if( i == static_cast<unsigned>(iMainDataIndex) )
out.m_SongTiming = pNewNotes->m_Timing;
pNewNotes->SetFilename(out.GetSongDir() + arrayBMSFileNames[i]);
out.AddSteps( pNewNotes );
}
else
+2
View File
@@ -2,11 +2,13 @@
#define NOTES_LOADER_BMS_H
class Song;
class Steps;
/** @brief Reads a Song from a set of .BMS files. */
namespace BMSLoader
{
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadFromDir( const RString &sDir, Song &out );
bool LoadNoteDataFromSimfile( const RString & cachePath, Steps &out );
}
#endif
+292 -213
View File
@@ -14,6 +14,8 @@
#include <map>
Difficulty DwiCompatibleStringToDifficulty( const RString& sDC );
static std::map<int,int> g_mapDanceNoteToNoteDataColumn;
/** @brief The different types of core DWI arrows and pads. */
@@ -168,6 +170,238 @@ Difficulty DwiCompatibleStringToDifficulty( const RString& sDC )
else return Difficulty_Invalid;
}
static StepsType GetTypeFromMode(const RString &mode)
{
if( mode == "SINGLE" )
return StepsType_dance_single;
else if( mode == "DOUBLE" )
return StepsType_dance_double;
else if( mode == "COUPLE" )
return StepsType_dance_couple;
else if( mode == "SOLO" )
return StepsType_dance_solo;
ASSERT_M(0, "Unrecognized DWI notes format " + mode + "!");
return StepsType_Invalid; // just in case.
}
static NoteData ParseNoteData(RString &step1, RString &step2,
Steps &out, const RString &path)
{
g_mapDanceNoteToNoteDataColumn.clear();
switch( out.m_StepsType )
{
case StepsType_dance_single:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
break;
case StepsType_dance_double:
case StepsType_dance_couple:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7;
break;
case StepsType_dance_solo:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5;
break;
DEFAULT_FAIL( out.m_StepsType );
}
NoteData newNoteData;
newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() );
for( int pad=0; pad<2; pad++ ) // foreach pad
{
RString sStepData;
switch( pad )
{
case 0:
sStepData = step1;
break;
case 1:
if( step2 == "" ) // no data
continue; // skip
sStepData = step2;
break;
DEFAULT_FAIL( pad );
}
sStepData.Replace("\n", "");
sStepData.Replace("\r", "");
sStepData.Replace("\t", "");
sStepData.Replace(" ", "");
double fCurrentBeat = 0;
double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
for( size_t i=0; i<sStepData.size(); )
{
char c = sStepData[i++];
switch( c )
{
// begins a series
case '(':
fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
break;
case '[':
fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
break;
case '{':
fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
break;
case '`':
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
break;
// ends a series
case ')':
case ']':
case '}':
case '\'':
case '>':
fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
break;
default: // this is a note character
{
if( c == '!' )
{
LOG->UserLog(
"Song file",
path,
"has an unexpected character: '!'." );
continue;
}
bool jump = false;
if( c == '<' )
{
/* Arr. Is this a jump or a 1/192 marker? */
if( Is192( sStepData, i ) )
{
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
break;
}
/* It's a jump.
* We need to keep reading notes until we hit a >. */
jump = true;
i++;
}
const int iIndex = BeatToNoteRow( (float)fCurrentBeat );
i--;
do {
c = sStepData[i++];
if( jump && c == '>' )
break;
int iCol1, iCol2;
DWIcharToNoteCol(
c,
(GameController)pad,
iCol1,
iCol2,
path );
if( iCol1 != -1 )
newNoteData.SetTapNote(iCol1,
iIndex,
TAP_ORIGINAL_TAP);
if( iCol2 != -1 )
newNoteData.SetTapNote(iCol2,
iIndex,
TAP_ORIGINAL_TAP);
if(i>=sStepData.length())
{
break;
//we ran out of data
//while looking for the ending > mark
}
if( sStepData[i] == '!' )
{
i++;
const char holdChar = sStepData[i++];
DWIcharToNoteCol(holdChar,
(GameController)pad,
iCol1,
iCol2,
path );
if( iCol1 != -1 )
newNoteData.SetTapNote(iCol1,
iIndex,
TAP_ORIGINAL_HOLD_HEAD);
if( iCol2 != -1 )
newNoteData.SetTapNote(iCol2,
iIndex,
TAP_ORIGINAL_HOLD_HEAD);
}
}
while( jump );
fCurrentBeat += fCurrentIncrementer;
}
break;
}
}
}
/* Fill in iDuration. */
for( int t=0; t<newNoteData.GetNumTracks(); ++t )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( newNoteData, t, iHeadRow )
{
TapNote tn = newNoteData.GetTapNote( t, iHeadRow );
if( tn.type != TapNote::hold_head )
continue;
int iTailRow = iHeadRow;
bool bFound = false;
while( !bFound && newNoteData.GetNextTapNoteRowForTrack(t, iTailRow) )
{
const TapNote &TailTap = newNoteData.GetTapNote( t, iTailRow );
if( TailTap.type == TapNote::empty )
continue;
newNoteData.SetTapNote( t, iTailRow, TAP_EMPTY );
tn.iDuration = iTailRow - iHeadRow;
newNoteData.SetTapNote( t, iHeadRow, tn );
bFound = true;
}
if( !bFound )
{
/* The hold was never closed. */
LOG->UserLog("Song file",
path,
"failed to close a hold note in \"%s\" on track %i",
DifficultyToString(out.GetDifficulty()).c_str(),
t);
newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY );
}
}
}
ASSERT( newNoteData.GetNumTracks() > 0 );
return newNoteData;
}
/**
* @brief Look through the notes tag to extract the data.
* @param sMode the steps type.
@@ -190,216 +424,17 @@ static bool LoadFromDWITokens(
{
CHECKPOINT_M( "DWILoader::LoadFromDWITokens()" );
out.m_StepsType = StepsType_Invalid;
out.m_StepsType = GetTypeFromMode(sMode);
if( sMode == "SINGLE" ) out.m_StepsType = StepsType_dance_single;
else if( sMode == "DOUBLE" ) out.m_StepsType = StepsType_dance_double;
else if( sMode == "COUPLE" ) out.m_StepsType = StepsType_dance_couple;
else if( sMode == "SOLO" ) out.m_StepsType = StepsType_dance_solo;
else
{
ASSERT_M(0, "Unrecognized DWI notes format " + sMode + "!");
out.m_StepsType = StepsType_dance_single;
}
out.SetMeter(StringToInt(sNumFeet));
g_mapDanceNoteToNoteDataColumn.clear();
switch( out.m_StepsType )
{
case StepsType_dance_single:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
break;
case StepsType_dance_double:
case StepsType_dance_couple:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7;
break;
case StepsType_dance_solo:
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4;
g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5;
break;
DEFAULT_FAIL( out.m_StepsType );
}
int iNumFeet = StringToInt(sNumFeet);
// out.SetDescription(sDescription); // Don't put garbage in the description.
out.SetMeter(iNumFeet);
out.SetDifficulty( DwiCompatibleStringToDifficulty(sDescription) );
NoteData newNoteData;
newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() );
for( int pad=0; pad<2; pad++ ) // foreach pad
{
RString sStepData;
switch( pad )
{
case 0:
sStepData = sStepData1;
break;
case 1:
if( sStepData2 == "" ) // no data
continue; // skip
sStepData = sStepData2;
break;
DEFAULT_FAIL( pad );
}
sStepData.Replace("\n", "");
sStepData.Replace("\r", "");
sStepData.Replace("\t", "");
sStepData.Replace(" ", "");
double fCurrentBeat = 0;
double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
for( size_t i=0; i<sStepData.size(); )
{
char c = sStepData[i++];
switch( c )
{
// begins a series
case '(':
fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
break;
case '[':
fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
break;
case '{':
fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
break;
case '`':
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
break;
// ends a series
case ')':
case ']':
case '}':
case '\'':
case '>':
fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
break;
default: // this is a note character
{
if( c == '!' )
{
LOG->UserLog( "Song file", sPath, "has an unexpected character: '!'." );
continue;
}
bool jump = false;
if( c == '<' )
{
/* Arr. Is this a jump or a 1/192 marker? */
if( Is192( sStepData, i ) )
{
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
break;
}
/* It's a jump. We need to keep reading notes until we hit a >. */
jump = true;
i++;
}
const int iIndex = BeatToNoteRow( (float)fCurrentBeat );
i--;
do {
c = sStepData[i++];
if( jump && c == '>' )
break;
int iCol1, iCol2;
DWIcharToNoteCol( c, (GameController)pad, iCol1, iCol2, sPath );
if( iCol1 != -1 )
newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_TAP);
if( iCol2 != -1 )
newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_TAP);
if(i>=sStepData.length()) {
break;//we ran out of data while looking for the ending > mark
}
if( sStepData[i] == '!' )
{
i++;
const char holdChar = sStepData[i++];
DWIcharToNoteCol( holdChar, (GameController)pad, iCol1, iCol2, sPath );
if( iCol1 != -1 )
newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_HOLD_HEAD);
if( iCol2 != -1 )
newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_HOLD_HEAD);
}
}
while( jump );
fCurrentBeat += fCurrentIncrementer;
}
break;
}
}
}
/* Fill in iDuration. */
for( int t=0; t<newNoteData.GetNumTracks(); ++t )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( newNoteData, t, iHeadRow )
{
TapNote tn = newNoteData.GetTapNote( t, iHeadRow );
if( tn.type != TapNote::hold_head )
continue;
int iTailRow = iHeadRow;
bool bFound = false;
while( !bFound && newNoteData.GetNextTapNoteRowForTrack(t, iTailRow) )
{
const TapNote &TailTap = newNoteData.GetTapNote( t, iTailRow );
if( TailTap.type == TapNote::empty )
continue;
newNoteData.SetTapNote( t, iTailRow, TAP_EMPTY );
tn.iDuration = iTailRow - iHeadRow;
newNoteData.SetTapNote( t, iHeadRow, tn );
bFound = true;
}
if( !bFound )
{
/* The hold was never closed. */
LOG->UserLog( "Song file", sPath, "failed to close a hold note in \"%s\" on track %i",
sDescription.c_str(), t );
newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY );
}
}
}
ASSERT( newNoteData.GetNumTracks() > 0 );
out.SetNoteData( newNoteData );
out.SetNoteData( ParseNoteData(sStepData1, sStepData2, out, sPath) );
out.TidyUpData();
out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved
return true;
}
@@ -442,17 +477,55 @@ void DWILoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.dwi"), out );
}
bool DWILoader::LoadNoteDataFromSimfile( const RString &path, Steps &out )
{
MsdFile msd;
if( !msd.ReadFile( path, false ) ) // don't unescape
{
LOG->UserLog("Song file",
path,
"couldn't be opened: %s",
msd.GetError().c_str() );
return false;
}
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
int iNumParams = msd.GetNumParams(i);
const MsdFile::value_t &params = msd.GetValue(i);
RString valueName = params[0];
if(valueName.EqualsNoCase("SINGLE") ||
valueName.EqualsNoCase("DOUBLE") ||
valueName.EqualsNoCase("COUPLE") ||
valueName.EqualsNoCase("SOLO") )
{
if (out.m_StepsType != GetTypeFromMode(valueName))
continue;
if (out.GetDifficulty() != DwiCompatibleStringToDifficulty(params[1]))
continue;
if (out.GetMeter() != StringToInt(params[2]))
continue;
RString step1 = params[3];
RString step2 = (iNumParams==5) ? params[4] : RString("");
out.SetNoteData(ParseNoteData(step1, step2, out, path));
return true;
}
}
return false;
}
bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &BlacklistedImages )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath_, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath_, "has more than one DWI file. There should be only one!" );
return false;
}
/* We should have exactly one; if we had none, we shouldn't have been called to begin with. */
ASSERT( aFileNames.size() == 1 );
const RString sPath = sPath_ + aFileNames[0];
@@ -466,6 +539,8 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
return false;
}
out.m_sSongFileName = sPath;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
int iNumParams = msd.GetNumParams(i);
@@ -513,14 +588,16 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( PREFSMAN->m_bQuirksMode )
{
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0, fBPM) );
}
else{
if( fBPM > 0.0f )
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0, fBPM) );
else
LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
LOG->UserLog("Song file",
sPath,
"has an invalid BPM change at beat %f, BPM %f.",
0.0f, fBPM );
}
}
else if( sValueName.EqualsNoCase("DISPLAYBPM") )
@@ -574,7 +651,7 @@ 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.m_SongTiming.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
out.m_SongTiming.AddSegment( SEGMENT_STOP_DELAY, new StopSegment(iFreezeRow, fFreezeSeconds) );
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds );
}
}
@@ -598,8 +675,8 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
float fBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fBPM > 0.0f )
{
BPMSegment bs( iStartIndex, fBPM );
out.m_SongTiming.AddBPMSegment( bs );
BPMSegment * bs = new BPMSegment( iStartIndex, fBPM );
out.m_SongTiming.AddSegment( SEGMENT_BPM, bs );
}
else
{
@@ -625,7 +702,10 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
sPath
);
if( pNewNotes->m_StepsType != StepsType_Invalid )
{
pNewNotes->SetFilename( sPath );
out.AddSteps( pNewNotes );
}
else
delete pNewNotes;
}
@@ -660,7 +740,6 @@ 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;
}
+3
View File
@@ -6,6 +6,7 @@
#include <set>
class Song;
class Steps;
/** @brief The DWILoader handles parsing the .dwi file. */
namespace DWILoader
@@ -24,6 +25,8 @@ namespace DWILoader
* @return its success or failure.
*/
bool LoadFromDir( const RString &sPath, Song &out, set<RString> &BlacklistedImages );
bool LoadNoteDataFromSimfile( const RString &path, Steps &out );
}
#endif
+22 -13
View File
@@ -15,22 +15,33 @@ void NotesLoaderJson::GetApplicableFiles( const RString &sPath, vector<RString>
GetDirListing( sPath + RString("*.json"), out );
}
void Deserialize(BPMSegment &seg, const Json::Value &root)
static void Deserialize(TimingSegment &seg_, const Json::Value &root)
{
seg.SetBeat((float)(root["Beat"].asDouble()));
seg.SetBPM((float)(root["BPM"].asDouble()));
}
static void Deserialize(StopSegment &seg, const Json::Value &root)
{
seg.SetBeat((float)(root["Beat"].asDouble()));
seg.SetPause((float)(root["Seconds"].asDouble()));
TimingSegment *seg = &seg_;
float fBeat = root["Beat"].asDouble();
seg->SetBeat(fBeat);
switch (seg->GetType())
{
case SEGMENT_BPM:
{
float fBPM = root["BPM"].asDouble();
static_cast<BPMSegment *>(seg)->SetBPM(fBPM);
break;
}
case SEGMENT_STOP_DELAY:
{
float fStop = root["Seconds"].asDouble();
static_cast<StopSegment *>(seg)->SetPause(fStop);
break;
}
}
}
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"] );
JsonUtil::DeserializeVectorPointers( td.allTimingSegments[SEGMENT_BPM], Deserialize, root["BpmSegments"] );
JsonUtil::DeserializeVectorPointers( td.allTimingSegments[SEGMENT_STOP_DELAY], Deserialize, root["StopSegments"] );
}
static void Deserialize(LyricSegment &o, const Json::Value &root)
@@ -148,8 +159,6 @@ static void Deserialize( Song &out, const Json::Value &root )
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();
+163 -88
View File
@@ -17,44 +17,10 @@ static void HandleBunki( TimingData &timing, const float fEarlyBPM,
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) );
timing.AddSegment( SEGMENT_BPM, new BPMSegment(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 )
static bool LoadFromKSFFile( const RString &sPath, Steps &out, Song &song, bool bKIUCompliant )
{
LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );
@@ -65,7 +31,10 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
return false;
}
int iTickCount = -1; // this is the value we read for TICKCOUNT
// this is the value we read for TICKCOUNT
int iTickCount = -1;
// used to adapt weird tickcounts
//float fScrollRatio = 1.0f; -- uncomment when ready to use.
vector<RString> vNoteRows;
// According to Aldo_MX, there is a default BPM and it's 60. -aj
@@ -85,13 +54,12 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
if (sValueName=="TITLE" || EndsWith(sValueName, "INTRO")
|| EndsWith(sValueName, "FILE") )
{
;
}
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
stepsTiming.AddBPMSegment( BPMSegment(0, BPM1) );
stepsTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
@@ -168,7 +136,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount );
return false;
}
stepsTiming.AddTickcountSegment(TickcountSegment(0, iTickCount));
stepsTiming.AddSegment( SEGMENT_TICKCOUNT, new TickcountSegment(0, iTickCount));
}
else if( sValueName=="DIFFICULTY" )
@@ -226,28 +194,43 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
out.SetDifficulty( Difficulty_Edit );
if( !out.GetMeter() ) out.SetMeter( 25 );
}
else if( sFName.find("wild") != string::npos || sFName.find("wd") != string::npos || sFName.find("crazy+") != string::npos || sFName.find("cz+") != string::npos || sFName.find("hardcore") != string::npos )
else if(sFName.find("wild") != string::npos ||
sFName.find("wd") != string::npos ||
sFName.find("crazy+") != string::npos ||
sFName.find("cz+") != string::npos ||
sFName.find("hardcore") != string::npos )
{
out.SetDifficulty( Difficulty_Challenge );
if( !out.GetMeter() ) out.SetMeter( 20 );
}
else if( sFName.find("crazy") != string::npos || sFName.find("cz") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("nm") != string::npos || sFName.find("crazydouble") != string::npos )
else if(sFName.find("crazy") != string::npos ||
sFName.find("cz") != string::npos ||
sFName.find("nightmare") != string::npos ||
sFName.find("nm") != string::npos ||
sFName.find("crazydouble") != string::npos )
{
out.SetDifficulty( Difficulty_Hard );
if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR.
}
else if( sFName.find("hard") != string::npos || sFName.find("hd") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("fs") != string::npos || sFName.find("double") != string::npos )
else if(sFName.find("hard") != string::npos ||
sFName.find("hd") != string::npos ||
sFName.find("freestyle") != string::npos ||
sFName.find("fs") != string::npos ||
sFName.find("double") != string::npos )
{
out.SetDifficulty( Difficulty_Medium );
if( !out.GetMeter() ) out.SetMeter( 8 );
}
else if( sFName.find("easy") != string::npos || sFName.find("ez") != string::npos || sFName.find("normal") != string::npos )
else if(sFName.find("easy") != string::npos ||
sFName.find("ez") != string::npos ||
sFName.find("normal") != string::npos )
{
// I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster
out.SetDifficulty( Difficulty_Easy );
if( !out.GetMeter() ) out.SetMeter( 4 );
}
else if( sFName.find("beginner") != string::npos || sFName.find("practice") != string::npos || sFName.find("pr") != string::npos )
else if(sFName.find("beginner") != string::npos ||
sFName.find("practice") != string::npos || sFName.find("pr") != string::npos )
{
out.SetDifficulty( Difficulty_Beginner );
if( !out.GetMeter() ) out.SetMeter( 4 );
@@ -261,10 +244,18 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
out.m_StepsType = StepsType_pump_single;
// Check for "halfdouble" before "double".
if( sFName.find("halfdouble") != string::npos || sFName.find("half-double") != string::npos || sFName.find("h_double") != string::npos || sFName.find("hdb") != string::npos )
if(sFName.find("halfdouble") != string::npos ||
sFName.find("half-double") != string::npos ||
sFName.find("h_double") != string::npos ||
sFName.find("hdb") != string::npos )
out.m_StepsType = StepsType_pump_halfdouble;
// Handle bDoublesChart from above as well. -aj
else if( sFName.find("double") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("db") != string::npos || sFName.find("nm") != string::npos || sFName.find("fs") != string::npos || bDoublesChart )
else if(sFName.find("double") != string::npos ||
sFName.find("nightmare") != string::npos ||
sFName.find("freestyle") != string::npos ||
sFName.find("db") != string::npos ||
sFName.find("nm") != string::npos ||
sFName.find("fs") != string::npos || bDoublesChart )
out.m_StepsType = StepsType_pump_double;
else if( sFName.find("_1") != string::npos )
out.m_StepsType = StepsType_pump_single;
@@ -311,34 +302,89 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) )
notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
else
notedata.AddHoldNote( t, iHoldStartRow[t], BeatToNoteRow(prevBeat) , TAP_ORIGINAL_HOLD_HEAD );
notedata.AddHoldNote(t,
iHoldStartRow[t],
BeatToNoteRow(prevBeat),
TAP_ORIGINAL_HOLD_HEAD );
}
}
/* have this row be the last moment in the song, unless
* a future step ends later. */
float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat);
if (curTime > song.GetSpecifiedLastSecond())
{
song.SetSpecifiedLastSecond(curTime);
}
break;
}
else if( BeginsWith(sRowString, "|") )
{
/*
if (bKIUCompliant)
{
// Log an error, ignore the line.
continue;
}
if ( !HandlePipeChars( stepsTiming, sRowString, fCurBeat, iTickCount ) )
{
// LOG it first.
}
continue;
*/
// gotta do something tricky here: if the bpm is below one then a couple of calculations
// for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will
// be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll
// segment will then be added based on approximations. -DaisuMaster
// eh better do it considering the tickcount (high tickcounts)
}
else
{
// Is this why improper ksf or some kiucompilant ksf mixed with dm05 ksf are ignored?? -DaisuMaster
//LOG->UserLog( "Song file", sPath, "has a RowString with an improper length \"%s\"; corrupt notes ignored.",
// sRowString.c_str() );
//return false;
// I'm making some experiments, please spare me...
//continue;
RString temp = sRowString.substr(2,sRowString.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(sRowString, "|T"))
{
// duh
iTickCount = static_cast<int>(numTemp);
// I have been owned by the man -DaisuMaster
stepsTiming.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) );
}
else if (BeginsWith(sRowString, "|B"))
{
// BPM
stepsTiming.SetBPMAtBeat( fCurBeat, numTemp );
}
else if (BeginsWith(sRowString, "|E"))
{
// DelayBeat
float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
stepsTiming.SetStopAtBeat( fCurBeat, fCurDelay, true );
}
else if (BeginsWith(sRowString, "|D"))
{
// Delays
float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += numTemp / 1000;
stepsTiming.SetStopAtBeat( fCurBeat, fCurDelay, true );
}
else if (BeginsWith(sRowString, "|M") || BeginsWith(sRowString, "|C"))
{
// multipliers/combo
stepsTiming.SetHitComboAtBeat( fCurBeat, static_cast<int>(numTemp) );
}
else if (BeginsWith(sRowString, "|S"))
{
// speed segments
}
else if (BeginsWith(sRowString, "|F"))
{
// fakes
}
else if (BeginsWith(sRowString, "|X"))
{
// scroll segments
stepsTiming.SetScrollAtBeat( fCurBeat, numTemp );
//return true;
}
continue;
}
// Half-doubles is offset; "0011111100000".
@@ -459,6 +505,20 @@ static void LoadTags( const RString &str, Song &out )
out.m_sArtist = artist;
}
static void ProcessTickcounts( const RString & value, int & ticks, TimingData & timing )
{
/* 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. */
ticks = StringToInt( value );
ticks = ticks > 0 ? ticks : 4;
// add a tickcount for those using the [Player]
// CheckpointsUseTimeSignatures metric. -aj
// It's not with timesigs now -DaisuMaster
TickcountSegment * tcs = new TickcountSegment(0, ticks > ROWS_PER_BEAT ? ROWS_PER_BEAT : ticks);
timing.AddSegment( SEGMENT_TICKCOUNT, tcs );
}
static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant )
{
MsdFile msd;
@@ -497,7 +557,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
out.m_SongTiming.AddBPMSegment( BPMSegment(0, BPM1) );
out.m_SongTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
@@ -533,21 +593,11 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if ( sValueName=="STARTTIME3" )
{
// STARTTIME3 only ensures this is a KIU compliant simfile.
bKIUCompliant = true;
//bKIUCompliant = true;
}
else if ( sValueName=="TICKCOUNT" )
{
/* 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 = StringToInt( sParams[1] );
iTickCount = iTickCount > 0 ? iTickCount : 4;
// add a tickcount for those using the [Player]
// CheckpointsUseTimeSignatures metric. -aj
// It's not with timesigs now -DaisuMaster
TickcountSegment tcs(0);
tcs.SetTicks(iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount);
out.m_SongTiming.AddTickcountSegment( tcs );
ProcessTickcounts(sParams[1], iTickCount, out.m_SongTiming);
}
else if ( sValueName=="STEP" )
{
@@ -557,10 +607,10 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
else if( sValueName=="DIFFICULTY" )
else if( sValueName=="DIFFICULTY" || sValueName=="PLAYER" )
{
/* DIFFICULTY is handled only in LoadFromKSFFile. Ignore it here. */
/* DIFFICULTY and PLAYER are handled only in LoadFromKSFFile.
Ignore those here. */
continue;
}
// New cases noted in Aldo_MX's code:
@@ -632,14 +682,10 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
}
// This is where the DMRequired test will take place.
if (BeginsWith(NoteRowString, "|T") || BeginsWith(NoteRowString, "|B") ||
BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") )
if ( BeginsWith( NoteRowString, "|" ) )
{
// have a static timing for everything
bDMRequired = true;
if ( !HandlePipeChars( out.m_SongTiming, NoteRowString, fCurBeat, iTickCount ) )
{
// LOG it first.
}
continue;
}
else
@@ -669,6 +715,22 @@ void KSFLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.ksf"), out );
}
bool KSFLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out )
{
bool KIUCompliant = false;
Song dummy;
if (!LoadGlobalData(cachePath, dummy, KIUCompliant))
return false;
Steps *notes = dummy.CreateSteps();
if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant))
{
KIUCompliant = true; // yeah, reusing a variable.
out.SetNoteData(notes->GetNoteData());
}
delete notes;
return KIUCompliant;
}
bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
{
LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() );
@@ -685,23 +747,36 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
* order (hopefully), it is best to use the LAST file for timing
* purposes, for that is the "normal", or easiest difficulty.
* Usually. */
// Nevermind, kiu compilancy is screwing things up:
// IE, I have two simfiles, oh wich each have four ksf files, the first one has
// the first ksf with directmove timing changes, and the rest are not, everything
// goes fine. In the other hand I have my second simfile with the first ksf file
// without directmove timing changes and the rest have changes, changes are not
// loaded due to kiucompilancy in the first ksf file.
// About the "normal" thing, my simfiles' ksfs uses non-standard naming so
// the last chart is usually nightmare or normal, I use easy and normal
// indistinctly for SM so it shouldn't matter, I use piu fiesta/ex naming
// for directmove though, and we're just gathering basic info anyway, and
// most of the time all the KSF files have the same info in the #TITLE:; section
unsigned files = arrayKSFFileNames.size();
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
RString dir = out.GetSongDir();
if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
return false;
out.m_sSongFileName = dir + arrayKSFFileNames[files - 1];
// load the Steps from the rest of the KSF files
for( unsigned i=0; i<files; i++ )
{
Steps* pNewNotes = out.CreateSteps();
if( !LoadFromKSFFile(out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
if( !LoadFromKSFFile(dir + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
{
delete pNewNotes;
continue;
}
pNewNotes->SetFilename(dir + arrayKSFFileNames[i]);
out.AddSteps( pNewNotes );
}
out.TidyUpData();
out.TidyUpData(false, true);
return true;
}
+2
View File
@@ -2,11 +2,13 @@
#define NOTES_LOADER_KSF_H
class Song;
class Steps;
/** @brief Reads a Song from a set of .KSF files. */
namespace KSFLoader
{
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadFromDir( const RString &sDir, Song &out );
bool LoadNoteDataFromSimfile( const RString & cachePath, Steps &out );
}
#endif
+10 -9
View File
@@ -677,21 +677,22 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
FOREACH_CONST( MidiFileIn::TempoChange, midi.tempoEvents_, iter )
{
BPMSegment bpmSeg;
bpmSeg.SetRow( MidiCountToNoteRow( iter->count ) );
BPMSegment * bpmSeg = NULL;
bpmSeg->SetRow( MidiCountToNoteRow( iter->count ) );
double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT);
bpmSeg.SetBPS( float( 1. / fSecondsPerBeat ) );
bpmSeg->SetBPS( float( 1. / fSecondsPerBeat ) );
songOut.m_SongTiming.AddBPMSegment( bpmSeg );
songOut.m_SongTiming.AddSegment( SEGMENT_BPM, bpmSeg );
}
FOREACH_CONST( MidiFileIn::TimeSignatureChange, midi.timeSignatureEvents_, iter )
{
TimeSignatureSegment seg(MidiCountToNoteRow( iter->count ),
iter->numerator,
iter->denominator);
TimeSignatureSegment * seg =
new TimeSignatureSegment(MidiCountToNoteRow( iter->count ),
iter->numerator,
iter->denominator);
songOut.m_SongTiming.AddTimeSignatureSegment( seg );
songOut.m_SongTiming.AddSegment( SEGMENT_TIME_SIG, seg );
}
@@ -955,7 +956,7 @@ bool MidiLoader::LoadFromDir( const RString &sDir, Song &out )
if( !LoadFromMidi(sDir+vsFiles[0], out) )
return false;
out.TidyUpData();
out.TidyUpData(false, true);
return true;
}
+84 -18
View File
@@ -573,7 +573,7 @@ static bool LoadFromPMSFile( 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 RString &sPath, const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map<RString,int> &idToKeySoundIndexOut )
{
RString sData;
if( GetTagFromMap(mapNameToData, "#title", sData) )
@@ -590,8 +590,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.m_SongTiming.AddBPMSegment( newSeg );
out.m_SongTiming.AddSegment( SEGMENT_BPM, new BPMSegment(0, fBPM) );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
@@ -612,26 +611,29 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
// this is keysound file name. Looks like "#WAV1A"
RString nData = it->second;
RString sWavID = sName.Right(2);
RString dir = out.GetSongDir();
if (dir.empty())
dir = Dirname(sPath);
/* Due to bugs in some programs, many PMS files have a "WAV" extension
* on files in the PMS 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) )
if( !IsAFile(dir+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) )
if( IsAFile(dir+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() );
if( !IsAFile(dir+nData) )
LOG->UserLog( "Song file", dir, "references key \"%s\" that can't be found", nData.c_str() );
sWavID.MakeUpper(); // HACK: undo the MakeLower()
out.m_vsKeysoundFile.push_back( nData );
@@ -693,9 +695,9 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( BeatToNoteRow(fBeat), fBPM );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() );
BPMSegment * newSeg = new BPMSegment( fBeat, fBPM );
out.m_SongTiming.AddSegment( SEGMENT_BPM, newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg->GetBPM() );
}
else
{
@@ -720,9 +722,9 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.m_SongTiming.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.GetPause() );
StopSegment * newSeg = new StopSegment( fBeat, fFreezeSecs );
out.m_SongTiming.AddSegment( SEGMENT_STOP_DELAY, newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg->GetPause() );
}
else
{
@@ -749,9 +751,11 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( iStepIndex, fBPM );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.GetBeat(), newSeg.GetBPM() );
BPMSegment * newSeg = new BPMSegment( iStepIndex, fBPM );
out.m_SongTiming.AddSegment( SEGMENT_BPM, newSeg );
LOG->Trace("Inserting new BPM change at beat %f, BPM %f",
newSeg->GetBeat(),
newSeg->GetBPM() );
}
else
@@ -809,6 +813,64 @@ void PMSLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.pms"), out );
}
bool PMSLoader::LoadNoteDataFromSimfile(const RString &cachePath, Steps &out)
{
Song dummy;
// TODO: Simplify this copy/paste from LoadFromDir.
vector<NameToData_t> BMSData;
BMSData.push_back(NameToData_t());
ReadPMSFile(cachePath, BMSData.back());
RString commonSubstring;
GetCommonTagFromMapList( BMSData, "#title", commonSubstring );
Steps *copy = dummy.CreateSteps();
copy->SetDifficulty( Difficulty_Medium );
RString sTag;
if( GetTagFromMap( BMSData[0], "#title", sTag ) && sTag.size() != commonSubstring.size() )
{
sTag = sTag.substr( commonSubstring.size(), sTag.size() - commonSubstring.size() );
sTag.MakeLower();
if( sTag.find('l') != sTag.npos )
{
unsigned lPos = sTag.find('l');
if( lPos > 2 && sTag.substr(lPos-2,4) == "solo" )
{
copy->SetDifficulty( Difficulty_Edit );
}
else
{
copy->SetDifficulty( Difficulty_Easy );
}
}
else if( sTag.find('a') != sTag.npos )
copy->SetDifficulty( Difficulty_Hard );
else if( sTag.find('b') != sTag.npos )
copy->SetDifficulty( Difficulty_Beginner );
}
if( commonSubstring == "" )
{
copy->SetDifficulty(Difficulty_Medium);
RString unused;
if (GetTagFromMap(BMSData[0], "#title#", sTag))
SearchForDifficulty(unused, copy);
}
MeasureToTimeSig_t sigAdjustments;
map<RString,int> idToKeysoundIndex;
ReadGlobalTags( cachePath, BMSData[0], dummy, sigAdjustments, idToKeysoundIndex );
const bool ok = LoadFromPMSFile( cachePath, BMSData[0], *copy, sigAdjustments, idToKeysoundIndex );
if( ok )
{
out.SetNoteData(copy->GetNoteData());
}
return ok;
}
bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
{
LOG->Trace( "Song::LoadFromPMSDir(%s)", sDir.c_str() );
@@ -914,7 +976,8 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
MeasureToTimeSig_t sigAdjustments;
map<RString,int> idToKeysoundIndex;
ReadGlobalTags( aPMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex );
ReadGlobalTags( sDir, aPMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex );
out.m_sSongFileName = out.GetSongDir() + arrayPMSFileNames[iMainDataIndex];
// Override what that global tag said about the title if we have a good substring.
// Prevents clobbering and catches "MySong (7keys)" / "MySong (Another) (7keys)"
@@ -929,7 +992,10 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
Steps* pNewNotes = apSteps[i];
const bool ok = LoadFromPMSFile( out.GetSongDir() + arrayPMSFileNames[i], aPMSData[i], *pNewNotes, sigAdjustments, idToKeysoundIndex );
if( ok )
{
pNewNotes->SetFilename(out.GetSongDir() + arrayPMSFileNames[i]);
out.AddSteps( pNewNotes );
}
else
delete pNewNotes;
}
@@ -940,7 +1006,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();
out.TidyUpData(false, true);
return true;
}
+2
View File
@@ -2,11 +2,13 @@
#define NOTES_LOADER_PMS_H
class Song;
class Steps;
/** @brief Reads a Song from a set of .PMS files. */
namespace PMSLoader
{
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadFromDir( const RString &sDir, Song &out );
bool LoadNoteDataFromSimfile(const RString & cachePath, Steps & out);
}
#endif
+320 -153
View File
@@ -13,10 +13,49 @@
#include "Attack.h"
#include "PrefsManager.h"
/** @brief The maximum file size for edits. */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB
void SMLoader::SetSongTitle(const RString & title)
{
this->songTitle = title;
}
void SMLoader::LoadFromSMTokens(
RString SMLoader::GetSongTitle() const
{
return this->songTitle;
}
bool SMLoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
// Need to break this up first.
RString tmp = "Song " + sPath + " has more than one";
LOG->UserLog(tmp, this->GetFileExtension(), "file. There can only be one!");
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSimfile( sPath + aFileNames[0], out );
}
float SMLoader::RowToBeat( RString line, const int rowsPerBeat )
{
RString backup = line;
Trim(line, "r");
Trim(line, "R");
if( backup != line )
{
return StringToFloat( line ) / rowsPerBeat;
}
else
{
return StringToFloat( line );
}
}
void SMLoader::LoadFromTokens(
RString sStepsType,
RString sDescription,
RString sDifficulty,
@@ -34,12 +73,13 @@ void SMLoader::LoadFromSMTokens(
Trim( sDifficulty );
Trim( sNoteData );
// LOG->Trace( "Steps::LoadFromSMTokens()" );
// LOG->Trace( "Steps::LoadFromTokens()" );
// 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.SetChartName(sDescription); // yeah, one more for good measure.
out.SetDifficulty( StringToDifficulty(sDifficulty) );
// Handle hacks that originated back when StepMania didn't have
@@ -56,34 +96,12 @@ void SMLoader::LoadFromSMTokens(
}
out.SetMeter( StringToInt(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();
}
void SMLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
{
GetDirListing( sPath + RString("*.sm"), out );
}
void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RString &sPath, const RString &sParam )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
@@ -109,26 +127,32 @@ void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RSt
}
}
void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
void SMLoader::ProcessAttackString( vector<RString> & attacks, MsdFile::value_t params )
{
for( unsigned s=1; s < params.params.size(); ++s )
{
RString tmp = params[s];
Trim(tmp);
if (tmp.size() > 0)
attacks.push_back( tmp );
}
}
void SMLoader::ProcessAttacks( AttackArray &attacks, MsdFile::value_t params )
{
// 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 )
for( unsigned j=1; j < params.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
split( params[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
Trim( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
@@ -138,6 +162,7 @@ void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
Trim(sBits[1]);
attack.sModifiers = sBits[1];
if( end != -9999 )
@@ -149,7 +174,7 @@ void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
attacks.push_back( attack );
}
}
}
@@ -171,10 +196,10 @@ void SMLoader::ProcessInstrumentTracks( Song &out, const RString &sParam )
}
}
bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
bool SMLoader::ProcessBPMs( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
split( line, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
@@ -186,22 +211,22 @@ bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
{
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.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"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 fBeat = RowToBeat( arrayBPMChangeValues[0], rowsPerBeat );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
@@ -211,8 +236,8 @@ bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat - negBeat);
out.AddWarpSegment( new_seg );
out.AddSegment(SEGMENT_WARP,
new WarpSegment(negBeat, endBeat - negBeat));
negBeat = -1;
negBPM = 1;
@@ -227,13 +252,13 @@ bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat - highspeedBeat);
out.AddWarpSegment( new_seg );
out.AddSegment(SEGMENT_WARP,
new WarpSegment(highspeedBeat, fBeat - highspeedBeat) );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
out.AddSegment(SEGMENT_BPM,
new BPMSegment(fBeat, fNewBPM));
}
}
}
@@ -242,10 +267,10 @@ bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
return bNotEmpty;
}
void SMLoader::ProcessStops( TimingData &out, const RString sParam )
void SMLoader::ProcessStops( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
split( line, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
@@ -257,27 +282,27 @@ void SMLoader::ProcessStops( TimingData &out, const RString sParam )
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.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] );
const float fFreezeBeat = RowToBeat( arrayFreezeValues[0], rowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
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 );
out.AddSegment(SEGMENT_WARP, new WarpSegment(negBeat, fSkipBeats));
negBeat = -1;
negPause = 0;
@@ -290,8 +315,8 @@ void SMLoader::ProcessStops( TimingData &out, const RString sParam )
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
out.AddSegment(SEGMENT_STOP_DELAY,
new StopSegment(fFreezeBeat, fFreezeSeconds));
}
}
@@ -299,19 +324,18 @@ void SMLoader::ProcessStops( TimingData &out, const RString sParam )
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
BPMSegment * oldBPM = out.GetBPMSegmentAtBeat(negBeat);
float fSecondsPerBeat = 60 / oldBPM->GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
out.AddSegment(SEGMENT_WARP, new WarpSegment(negBeat, fSkipBeats));
}
}
void SMLoader::ProcessDelays( TimingData &out, const RString sParam )
void SMLoader::ProcessDelays( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
split( line, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
@@ -319,33 +343,37 @@ void SMLoader::ProcessDelays( TimingData &out, const RString sParam )
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.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayDelayValues[0] );
const float fFreezeBeat = RowToBeat( arrayDelayValues[0], rowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( fFreezeBeat, fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.SetBeat(fFreezeBeat);
new_seg.SetPause(fFreezeSeconds);
StopSegment * new_seg = new StopSegment(fFreezeBeat,
fFreezeSeconds,
true);
// 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 );
out.AddSegment( SEGMENT_STOP_DELAY, new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
LOG->UserLog(
"Song file",
this->GetSongTitle(),
"has an invalid delay at beat %f, length %f.",
fFreezeBeat, fFreezeSeconds );
}
}
void SMLoader::ProcessTimeSignatures( TimingData &out, const RString sParam )
void SMLoader::ProcessTimeSignatures( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
split( line, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
@@ -354,40 +382,55 @@ void SMLoader::ProcessTimeSignatures( TimingData &out, const RString sParam )
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
const float fBeat = RowToBeat( vs2[0], rowsPerBeat );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), StringToInt( vs2[2] ));
TimeSignatureSegment * seg =
new TimeSignatureSegment(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 );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with beat %f.",
fBeat );
continue;
}
if( seg.GetNum() < 1 )
if( seg->GetNum() < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.GetNum() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with beat %f, iNumerator %i.",
fBeat, seg->GetNum() );
continue;
}
if( seg.GetDen() < 1 )
if( seg->GetDen() < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.GetDen() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with beat %f, iDenominator %i.",
fBeat, seg->GetDen() );
continue;
}
out.AddTimeSignatureSegment( seg );
out.AddSegment( SEGMENT_TIME_SIG, seg );
}
}
void SMLoader::ProcessTickcounts( TimingData &out, const RString sParam )
void SMLoader::ProcessTickcounts( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
split( line, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
@@ -395,17 +438,109 @@ void SMLoader::ProcessTickcounts( TimingData &out, const RString sParam )
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.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
const float fTickcountBeat = RowToBeat( arrayTickcountValues[0], rowsPerBeat );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
out.AddSegment( SEGMENT_TICKCOUNT,
new TickcountSegment(fTickcountBeat, iTicks) );
}
}
void SMLoader::ProcessSpeeds( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> vs1;
split( line, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2[0] == 0 && vs2.size() == 2 ) // First one always seems to have 2.
{
vs2.push_back("0");
}
if( vs2.size() == 3 ) // use beats by default.
{
vs2.push_back("0");
}
if( vs2.size() < 4 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = RowToBeat( vs2[0], rowsPerBeat );
SpeedSegment * seg = new SpeedSegment(fBeat,
StringToFloat( vs2[1] ),
StringToFloat( vs2[2] ));
seg->SetUnit(StringToInt(vs2[3]));
if( fBeat < 0 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with beat %f.",
fBeat );
continue;
}
if( seg->GetLength() < 0 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with beat %f, length %f.",
fBeat, seg->GetLength() );
continue;
}
out.AddSegment( SEGMENT_SPEED, seg );
}
}
void SMLoader::ProcessFakes( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayFakeExpressions;
split( line, ",", arrayFakeExpressions );
for( unsigned b=0; b<arrayFakeExpressions.size(); b++ )
{
vector<RString> arrayFakeValues;
split( arrayFakeExpressions[b], "=", arrayFakeValues );
if( arrayFakeValues.size() != 2 )
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #FAKES value \"%s\" (must have exactly one '='), ignored.",
arrayFakeExpressions[b].c_str() );
continue;
}
const float fBeat = RowToBeat( arrayFakeValues[0], rowsPerBeat );
const float fNewBeat = StringToFloat( arrayFakeValues[1] );
if(fNewBeat > 0)
out.AddSegment( SEGMENT_FAKE, new FakeSegment(fBeat, fNewBeat) );
else
{
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Fake at beat %f, BPM %f.",
fBeat, fNewBeat );
}
}
}
@@ -495,7 +630,66 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString
return aBGChangeValues.size() >= 2;
}
bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache )
bool SMLoader::LoadNoteDataFromSimfile( const RString &path, Steps &out )
{
MsdFile msd;
if( !msd.ReadFile( path, true ) ) // unescape
{
LOG->UserLog("Song file",
path,
"couldn't be opened: %s",
msd.GetError().c_str() );
return false;
}
for (unsigned i = 0; i<msd.GetNumValues(); i++)
{
int iNumParams = msd.GetNumParams(i);
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
// The only tag we care about is the #NOTES tag.
if( sValueName=="NOTES" || sValueName=="NOTES2" )
{
if( iNumParams < 7 )
{
LOG->UserLog("Song file",
path,
"has %d fields in a #NOTES tag, but should have at least 7.",
iNumParams );
continue;
}
RString stepsType = sParams[1];
RString description = sParams[2];
RString difficulty = sParams[3];
Trim(stepsType);
Trim(description);
Trim(difficulty);
// Remember our old versions.
if (difficulty.CompareNoCase("smaniac") == 0)
{
difficulty = "Challenge";
}
if(!(out.m_StepsType == GAMEMAN->StringToStepsType( stepsType ) &&
out.GetDescription() == description &&
out.GetDifficulty() == StringToDifficulty(difficulty)))
{
continue;
}
RString noteData = sParams[6];
Trim( noteData );
out.SetSMNoteData( noteData );
out.TidyUpData();
return true;
}
}
return false;
}
bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache )
{
LOG->Trace( "Song::LoadFromSMFile(%s)", sPath.c_str() );
@@ -507,6 +701,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
}
out.m_SongTiming.m_sFile = sPath;
out.m_sSongFileName = sPath;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -519,7 +714,10 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
/* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically
* splitting other formats that *don't* natively support #SUBTITLE. */
if( sValueName=="TITLE" )
{
out.m_sMainTitle = sParams[1];
this->SetSongTitle(sParams[1]);
}
else if( sValueName=="SUBTITLE" )
out.m_sSubTitle = sParams[1];
@@ -601,37 +799,19 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
}
else if( sValueName=="LASTBEATHINT" )
out.m_fSpecifiedLastBeat = StringToFloat( sParams[1] );
{
// unable to identify at this point: ignore
}
else if( sValueName=="MUSICBYTES" )
; /* ignore */
/* We calculate these. Some SMs in circulation have bogus values for
* these, so make sure we always calculate it ourself. */
else if( sValueName=="FIRSTBEAT" )
// cache tags from older SM files: ignore.
else if(sValueName=="FIRSTBEAT" || sValueName=="LASTBEAT" ||
sValueName=="SONGFILENAME" || sValueName=="HASMUSIC" ||
sValueName=="HASBANNER")
{
if( bFromCache )
out.m_fFirstBeat = StringToFloat( sParams[1] );
}
else if( sValueName=="LASTBEAT" )
{
if( bFromCache )
out.m_fLastBeat = StringToFloat( sParams[1] );
}
else if( sValueName=="SONGFILENAME" )
{
if( bFromCache )
out.m_sSongFileName = sParams[1];
}
else if( sValueName=="HASMUSIC" )
{
if( bFromCache )
out.m_bHasMusic = StringToInt( sParams[1] ) != 0;
}
else if( sValueName=="HASBANNER" )
{
if( bFromCache )
out.m_bHasBanner = StringToInt( sParams[1] ) != 0;
;
}
else if( sValueName=="SAMPLESTART" )
@@ -707,7 +887,8 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
// Attacks loaded from file
else if( sValueName=="ATTACKS" )
{
ProcessAttacks( out, sParams );
ProcessAttackString(out.m_sAttackString, sParams);
ProcessAttacks(out.m_Attacks, sParams);
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -719,7 +900,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
}
Steps* pNewNotes = out.CreateSteps();
LoadFromSMTokens(
LoadFromTokens(
sParams[1],
sParams[2],
sParams[3],
@@ -728,6 +909,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
sParams[6],
*pNewNotes );
pNewNotes->SetFilename(sPath);
out.AddSteps( pNewNotes );
}
// XXX: Does anyone know what LEADTRACK is for? -Wolfman2000
@@ -738,38 +920,12 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
}
// Ensure all warps from negative time changes are in order.
sort(out.m_SongTiming.m_WarpSegments.begin(), out.m_SongTiming.m_WarpSegments.end());
vector<TimingSegment *> &warps = out.m_SongTiming.allTimingSegments[SEGMENT_WARP];
sort(warps.begin(), warps.end());
TidyUpData( out, bFromCache );
return true;
}
bool SMLoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SM file. There can be only one (unless you are using TougaKiryuu's AnimeMix files somehow, which assume a different version of StepMania)!" );
return false;
/*
for( unsigned i=0; i<aFileNames.size(); i++ )
{
if(!LoadFromSMFile( sPath + aFileNames[i], out ))
return false;
}
return true;
*/
}
ASSERT( aFileNames.size() == 1 );
/* We should have at least one; if we had none, we shouldn't have been
* called to begin with. */
//ASSERT( aFileNames.size() >= 1 );
return LoadFromSMFile( sPath + aFileNames[0], out );
}
bool SMLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
LOG->Trace( "SMLoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
@@ -819,6 +975,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
}
RString sSongFullTitle = sParams[1];
this->SetSongTitle(sParams[1]);
sSongFullTitle.Replace( '\\', '/' );
pSong = SONGMAN->FindSong( sSongFullTitle );
@@ -853,7 +1010,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
return true;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMTokens(
LoadFromTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
@@ -881,6 +1038,11 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
}
void SMLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
{
GetDirListing( sPath + RString("*" + this->GetFileExtension() ), out );
}
void SMLoader::TidyUpData( Song &song, bool bFromCache )
{
/*
@@ -917,9 +1079,10 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache )
if( bFromCache )
break;
float lastBeat = song.GetLastBeat();
/* If BGChanges already exist after the last beat, don't add the
* background in the middle. */
if( !bg.empty() && bg.back().m_fStartBeat-0.0001f >= song.m_fLastBeat )
if( !bg.empty() && bg.back().m_fStartBeat-0.0001f >= lastBeat )
break;
// If the last BGA is already the song BGA, don't add a duplicate.
@@ -929,10 +1092,14 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache )
if( !IsAFile( song.GetBackgroundPath() ) )
break;
bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) );
bg.push_back( BackgroundChange(lastBeat,song.m_sBackgroundFile) );
} while(0);
}
song.TidyUpData( bFromCache );
if (bFromCache)
{
song.TidyUpData( bFromCache, true );
}
}
/*
+176 -22
View File
@@ -3,6 +3,7 @@
#include "GameConstantsAndTypes.h"
#include "BackgroundUtil.h"
#include "Attack.h"
#include "MsdFile.h" // we require the struct from here.
class Song;
@@ -15,33 +16,186 @@ class TimingData;
* 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
{
void LoadFromSMTokens( RString sStepsType, RString sDescription, RString sDifficulty,
RString sMeter, RString sRadarValues, RString sNoteData, Steps &out );
bool LoadFromDir( const RString &sPath, Song &out );
void TidyUpData( Song &song, bool bFromCache );
/** @brief The maximum file size for edits. */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB
bool LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache = false );
void GetApplicableFiles( const RString &sPath, vector<RString> &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 );
/** @brief Reads a Song from an .SM file. */
struct SMLoader
{
SMLoader() : fileExt(".sm"), songTitle() {}
SMLoader(RString ext) : fileExt(ext), songTitle() {}
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,
virtual ~SMLoader() {}
/**
* @brief Attempt to load a song from a specified path.
* @param sPath a const reference to the path on the hard drive to check.
* @param out a reference to the Song that will retrieve the song information.
* @return its success or failure.
*/
virtual bool LoadFromDir( const RString &sPath, Song &out );
/**
* @brief Perform some cleanup on the loaded song.
* @param song a reference to the song that may need cleaning up.
* @param bFromCache a flag to determine if this song is loaded from a cache file.
*/
virtual void TidyUpData( Song &song, bool bFromCache );
/**
* @brief Retrieve the relevant notedata from the simfile.
* @param path the path where the simfile lives.
* @param out the Steps we are loading the data into. */
virtual bool LoadNoteDataFromSimfile(const RString &path, Steps &out );
/**
* @brief Attempt to load the specified sm file.
* @param sPath a const reference to the path on the hard drive to check.
* @param out a reference to the Song that will retrieve the song information.
* @param bFromCache a check to see if we are getting certain information from the cache file.
* @return its success or failure.
*/
virtual bool LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache = false );
/**
* @brief Retrieve the list of .sm files.
* @param sPath a const reference to the path on the hard drive to check.
* @param out a vector of files found in the path.
*/
virtual void GetApplicableFiles( const RString &sPath, vector<RString> &out );
virtual bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
virtual bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
virtual bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
virtual bool LoadFromBGChangesString(BackgroundChange &change,
const RString &sBGChangeExpression );
/**
* @brief Process the BPM Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose.
* @return true if there was at least one segment found, false otherwise. */
bool ProcessBPMs(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
/**
* @brief Process the Stop Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
void ProcessStops(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
/**
* @brief Process the Delay Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
void ProcessDelays(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
/**
* @brief Process the Time Signature Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
void ProcessTimeSignatures(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
/**
* @brief Process the Tickcount Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
void ProcessTickcounts(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
/**
* @brief Process the Speed Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
virtual void ProcessSpeeds(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
virtual void ProcessCombos(TimingData & out,
const RString line,
const int rowsPerBeat = -1) {}
/**
* @brief Process the Fake Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
virtual void ProcessFakes(TimingData & out,
const RString line,
const int rowsPerBeat = -1);
virtual void ProcessBGChanges( Song &out, const RString &sValueName,
const RString &sPath, const RString &sParam );
void ProcessAttacks( Song &out, MsdFile::value_t sParams );
/**
* @brief Put the attacks in the attacks string.
* @param attacks the attack string.
* @param params the params from the simfile. */
virtual void ProcessAttackString(vector<RString> &attacks, MsdFile::value_t params);
/**
* @brief Put the attacks in the attacks array.
* @param attacks the attacks array.
* @param params the params from the simfile. */
void ProcessAttacks( AttackArray &attacks, MsdFile::value_t params );
void ProcessInstrumentTracks( Song &out, const RString &sParam );
}
/**
* @brief Convert a row value to the proper beat value.
*
* This is primarily used for assistance with converting SMA files.
* @param line The line that contains the value.
* @param rowsPerBeat the number of rows per beat according to the original file.
* @return the converted beat value. */
float RowToBeat(RString line, const int rowsPerBeat);
protected:
/**
* @brief Process the different tokens we have available to get NoteData.
* @param stepsType The current StepsType.
* @param description The description of the chart.
* @param difficulty The difficulty (in words) of the chart.
* @param meter the difficulty (in numbers) of the chart.
* @param radarValues the calculated radar values.
* @param noteData the note data itself.
* @param out the Steps getting the data. */
virtual void LoadFromTokens(RString sStepsType,
RString sDescription,
RString sDifficulty,
RString sMeter,
RString sRadarValues,
RString sNoteData,
Steps &out);
/**
* @brief Retrieve the file extension associated with this loader.
* @return the file extension. */
RString GetFileExtension() const { return fileExt; }
/**
* @brief Set the song title.
* @param t the song title. */
virtual void SetSongTitle(const RString & title);
/**
* @brief Get the song title.
* @return the song title. */
virtual RString GetSongTitle() const;
private:
/** @brief The file extension in use. */
const RString fileExt;
/** @brief The song title that is being processed. */
RString songTitle;
};
#endif
+85 -460
View File
@@ -14,240 +14,6 @@
#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( fFreezeBeat, fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.SetBeat(fFreezeBeat);
new_seg.SetPause(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;
@@ -257,16 +23,23 @@ void SMALoader::ProcessMultipliers( TimingData &out, const int iRowsPerBeat, con
{
vector<RString> arrayMultiplierValues;
split( arrayMultiplierExpressions[f], "=", arrayMultiplierValues );
if( arrayMultiplierValues.size() != 2 )
unsigned size = arrayMultiplierValues.size();
if( size < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #MULTIPLIER value \"%s\" (must have exactly one '='), ignored.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #MULTIPLIER value \"%s\" (must have at least 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 );
const int iCombos = StringToInt( arrayMultiplierValues[1] ); // always true.
// hoping I'm right here: SMA files can use 6 values after the row/beat.
const int iMisses = (size == 2 || size == 4 ?
iCombos :
StringToInt(arrayMultiplierValues[2]));
out.AddSegment(SEGMENT_COMBO,
new ComboSegment( fComboBeat, iCombos, iMisses ));
}
}
@@ -282,39 +55,45 @@ void SMALoader::ProcessBeatsPerMeasure( TimingData &out, const RString sParam )
if( vs2.size() < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid beats per measure change with %i values.", (int)vs2.size() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid beats per measure change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), 4 );
TimeSignatureSegment * seg = new TimeSignatureSegment(fBeat,
StringToInt(vs2[1]),
4 );
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with beat %f.",
fBeat );
continue;
}
if( seg.GetNum() < 1 )
if( seg->GetNum() < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.GetNum() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid time signature change with beat %f, iNumerator %i.",
fBeat, seg->GetNum() );
continue;
}
out.AddTimeSignatureSegment( seg );
out.AddSegment( SEGMENT_TIME_SIG, seg );
}
}
float BeatToSeconds(float fromBeat, RString toSomething)
{
return 0;
}
void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam )
void SMALoader::ProcessSpeeds( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
split( line, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
@@ -331,11 +110,14 @@ void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RS
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with %i values.", (int)vs2.size() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = RowToBeat( vs2[0], iRowsPerBeat );
const float fBeat = RowToBeat( vs2[0], rowsPerBeat );
RString backup = vs2[2];
Trim(vs2[2], "s");
@@ -343,76 +125,34 @@ void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RS
unsigned short tmp = ((backup != vs2[2]) ? 1 : 0);
SpeedSegment seg(fBeat, StringToFloat( vs2[1] ), StringToFloat(vs2[2]), tmp);
//seg.SetUnit(tmp);
SpeedSegment * seg = new SpeedSegment(fBeat,
StringToFloat( vs2[1] ),
StringToFloat(vs2[2]),
tmp);
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f.", fBeat );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with beat %f.",
fBeat );
continue;
}
if( seg.GetLength() < 0 )
if( seg->GetLength() < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f, length %f.", fBeat, seg.GetLength() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an speed change with beat %f, length %f.",
fBeat, seg->GetLength() );
continue;
}
out.AddSpeedSegment( seg );
out.AddSegment( SEGMENT_SPEED, 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,
RString sDifficulty,
RString sMeter,
RString sRadarValues,
RString sNoteData,
Steps &out
)
{
SMLoader::LoadFromSMTokens( sStepsType, sDescription,
sDifficulty, sMeter, sRadarValues,
sNoteData, out );
}
void SMALoader::TidyUpData( Song &song, bool bFromCache )
{
SMLoader::TidyUpData( song, bFromCache );
}
bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache )
{
LOG->Trace( "Song::LoadFromSMAFile(%s)", sPath.c_str() );
@@ -424,6 +164,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
out.m_sSongFileName = sPath;
int state = SMA_GETTING_SONG_INFO;
Steps* pNewNotes = NULL;
@@ -441,7 +182,10 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
/* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically
* splitting other formats that *don't* natively support #SUBTITLE. */
if( sValueName=="TITLE" )
{
out.m_sMainTitle = sParams[1];
this->SetSongTitle(sParams[1]);
}
else if( sValueName=="SUBTITLE" )
out.m_sSubTitle = sParams[1];
@@ -491,30 +235,16 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
else if( sValueName=="LASTBEATHINT" )
out.m_fSpecifiedLastBeat = StringToFloat( sParams[1] );
{
// can't identify at this position: ignore.
}
else if( sValueName=="MUSICBYTES" )
; /* ignore */
/* We calculate these. Some SMs in circulation have bogus values for
* these, so make sure we always calculate it ourself. */
else if( sValueName=="FIRSTBEAT" )
{
;
}
else if( sValueName=="LASTBEAT" )
{
;
}
else if( sValueName=="SONGFILENAME" )
{
;
}
else if( sValueName=="HASMUSIC" )
{
;
}
else if( sValueName=="HASBANNER" )
// Cache tags: ignore.
else if (sValueName=="FIRSTBEAT" || sValueName=="LASTBEAT" ||
sValueName=="SONGFILENAME" || sValueName=="HASMUSIC" ||
sValueName=="HASBANNER" )
{
;
}
@@ -603,7 +333,10 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
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() );
LOG->UserLog("Song file",
sPath,
"has an unknown #SELECTABLE value, \"%s\"; ignored.",
sParams[1].c_str() );
}
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
@@ -635,28 +368,28 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBPMs( timing, iRowsPerBeat, sParams[1] );
ProcessBPMs( timing, sParams[1], iRowsPerBeat );
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessStops( timing, iRowsPerBeat, sParams[1] );
ProcessStops( timing, sParams[1], iRowsPerBeat );
}
else if( sValueName=="DELAYS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessDelays( timing, iRowsPerBeat, sParams[1] );
ProcessDelays( timing, sParams[1], iRowsPerBeat );
}
else if( sValueName=="TICKCOUNT" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessTickcounts( timing, iRowsPerBeat, sParams[1] );
ProcessTickcounts( timing, sParams[1], iRowsPerBeat );
}
else if( sValueName=="SPEED" )
@@ -665,7 +398,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
? pNewNotes->m_Timing : out.m_SongTiming);
RString tmp = sParams[1];
Trim( tmp );
ProcessSpeeds( timing, iRowsPerBeat, tmp );
ProcessSpeeds( timing, tmp, iRowsPerBeat );
}
else if( sValueName=="MULTIPLIER" )
@@ -679,7 +412,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessFakes( timing, iRowsPerBeat, sParams[1] );
ProcessFakes( timing, sParams[1], iRowsPerBeat );
}
else if( sValueName=="METERTYPE" )
@@ -695,18 +428,22 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
// Attacks loaded from file
else if( sValueName=="ATTACKS" )
{
SMLoader::ProcessAttacks( out, sParams );
ProcessAttackString(out.m_sAttackString, sParams);
ProcessAttacks(out.m_Attacks, sParams);
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
{
if( iNumParams < 7 )
{
LOG->UserLog( "Song file", sPath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams );
LOG->UserLog("Song file",
sPath,
"has %d fields in a #NOTES tag, but should have at least 7.",
iNumParams );
continue;
}
LoadFromSMATokens(
LoadFromTokens(
sParams[1],
sParams[2],
sParams[3],
@@ -714,131 +451,19 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
sParams[5],
sParams[6],
*pNewNotes );
pNewNotes->SetFilename(sPath);
out.AddSteps( pNewNotes );
}
else if( sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" )
;
else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
LOG->UserLog("Song file",
sPath,
"has an unexpected value named \"%s\".",
sValueName.c_str() );
}
TidyUpData(out, false);
out.TidyUpData();
return true;
}
void SMALoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
{
GetDirListing( sPath + RString("*.sma"), out );
}
bool SMALoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
LOG->Trace( "SMALoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath );
if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES )
{
LOG->UserLog( "Edit file", sEditFilePath, "is unreasonably large. It won't be loaded." );
return false;
}
MsdFile msd;
if( !msd.ReadFile( sEditFilePath, true ) ) // unescape
{
LOG->UserLog( "Edit file", sEditFilePath, "couldn't be opened: %s", msd.GetError().c_str() );
return false;
}
return LoadEditFromMsd( msd, sEditFilePath, slot, bAddStepsToSong );
}
bool SMALoader::LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot )
{
MsdFile msd;
msd.ReadFromString( sBuffer, true ); // unescape
return LoadEditFromMsd( msd, sEditFilePath, slot, true );
}
bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
Song* pSong = NULL;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
int iNumParams = msd.GetNumParams(i);
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
// handle the data
if( sValueName=="SONG" )
{
if( pSong )
{
LOG->UserLog( "Edit file", sEditFilePath, "has more than one #SONG tag." );
return false;
}
RString sSongFullTitle = sParams[1];
sSongFullTitle.Replace( '\\', '/' );
pSong = SONGMAN->FindSong( sSongFullTitle );
if( pSong == NULL )
{
LOG->UserLog( "Edit file", sEditFilePath, "requires a song \"%s\" that isn't present.", sSongFullTitle.c_str() );
return false;
}
if( pSong->GetNumStepsLoadedFromProfile(slot) >= MAX_EDITS_PER_SONG_PER_PROFILE )
{
LOG->UserLog( "Song file", sSongFullTitle, "already has the maximum number of edits allowed for ProfileSlotP%d.", slot+1 );
return false;
}
}
else if( sValueName=="NOTES" )
{
if( pSong == NULL )
{
LOG->UserLog( "Edit file", sEditFilePath, "doesn't have a #SONG tag preceeding the first #NOTES tag." );
return false;
}
if( iNumParams < 7 )
{
LOG->UserLog( "Edit file", sEditFilePath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams );
continue;
}
if( !bAddStepsToSong )
return true;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMATokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
pNewNotes->SetLoadedFromProfile( slot );
pNewNotes->SetDifficulty( Difficulty_Edit );
pNewNotes->SetFilename( sEditFilePath );
if( pSong->IsEditAlreadyLoaded(pNewNotes) )
{
LOG->UserLog( "Edit file", sEditFilePath, "is a duplicate of another edit that was already loaded." );
SAFE_DELETE( pNewNotes );
return false;
}
pSong->AddSteps( pNewNotes );
return true; // Only allow one Steps per edit file!
}
else
{
LOG->UserLog( "Edit file", sEditFilePath, "has an unexpected value \"%s\".", sValueName.c_str() );
}
}
out.TidyUpData(false, true);
return true;
}
+12 -27
View File
@@ -2,6 +2,7 @@
#define NOTES_LOADER_SMA_H
#include "GameConstantsAndTypes.h"
#include "NotesLoaderSM.h"
#include "BackgroundUtil.h"
class MsdFile;
@@ -20,37 +21,21 @@ enum SMALoadingStates
};
/** @brief Reads a Song from a .SMA file. */
namespace SMALoader
{
void LoadFromSMATokens( RString sStepsType,
RString sDescription,
RString sDifficulty,
RString sMeter,
RString sRadarValues,
RString sNoteData,
Steps &out );
struct SMALoader : public SMLoader
{
SMALoader() : SMLoader(".sma") {}
bool LoadFromDir( const RString &sPath, Song &out );
void TidyUpData( Song &song, bool bFromCache );
bool LoadFromSMAFile( const RString &sPath, Song &out );
void GetApplicableFiles( const RString &sPath, vector<RString> &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 );
virtual bool LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache = false );
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 );
/**
* @brief Process the Speed Segments from the string.
* @param out the TimingData being modified.
* @param line the string in question.
* @param rowsPerBeat the number of rows per beat for this purpose. */
virtual void ProcessSpeeds( TimingData &out, const RString line, const int rowsPerBeat );
};
#endif
+239 -165
View File
@@ -14,38 +14,6 @@
#include "Attack.h"
#include "PrefsManager.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
/**
* @brief Attempt to load any background changes in use by this song.
* @param change a reference to the background change.
* @param sBGChangeExpression a reference to the list of changes to be made.
* @return its success or failure.
*/
bool LoadFromBGSSCChangesString( BackgroundChange &change, const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString( change, sBGChangeExpression );
}
bool SSCLoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SSC file. Only one SSC file is allowed per song." );
return false;
}
ASSERT( aFileNames.size() == 1 ); // Ensure one was found entirely.
return LoadFromSSCFile( sPath + aFileNames[0], out );
}
void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float fVersion )
{
vector<RString> arrayWarpExpressions;
@@ -55,10 +23,11 @@ void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float
{
vector<RString> arrayWarpValues;
split( arrayWarpExpressions[b], "=", arrayWarpValues );
// XXX: Hard to tell which file caused this.
if( arrayWarpValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #WARPS value \"%s\" (must have exactly one '='), ignored.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #WARPS value \"%s\" (must have exactly one '='), ignored.",
arrayWarpExpressions[b].c_str() );
continue;
}
@@ -68,13 +37,16 @@ void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float
// Early versions were absolute in beats. They should be relative.
if( ( fVersion < VERSION_SPLIT_TIMING && fNewBeat > fBeat ) )
{
out.AddWarpSegment( WarpSegment(fBeat, fNewBeat - fBeat) );
out.AddSegment( SEGMENT_WARP, new WarpSegment(fBeat, fNewBeat - fBeat) );
}
else if( fNewBeat > 0 )
out.AddWarpSegment( WarpSegment(fBeat, fNewBeat) );
out.AddSegment( SEGMENT_WARP, new WarpSegment(fBeat, fNewBeat) );
else
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Warp at beat %f, BPM %f.", fBeat, fNewBeat );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Warp at beat %f, BPM %f.",
fBeat, fNewBeat );
}
}
}
@@ -90,7 +62,9 @@ void SSCLoader::ProcessLabels( TimingData &out, const RString sParam )
split( arrayLabelExpressions[b], "=", arrayLabelValues );
if( arrayLabelValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #LABELS value \"%s\" (must have exactly one '='), ignored.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #LABELS value \"%s\" (must have exactly one '='), ignored.",
arrayLabelExpressions[b].c_str() );
continue;
}
@@ -99,81 +73,40 @@ void SSCLoader::ProcessLabels( TimingData &out, const RString sParam )
RString sLabel = arrayLabelValues[1];
TrimRight(sLabel);
if( fBeat >= 0.0f )
out.AddLabelSegment( LabelSegment(fBeat, sLabel) );
out.AddSegment( SEGMENT_LABEL, new LabelSegment(fBeat, sLabel) );
else
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Label at beat %f called %s.", fBeat, sLabel.c_str() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid Label at beat %f called %s.",
fBeat, sLabel.c_str() );
}
}
}
void SSCLoader::ProcessCombos( TimingData &out, const RString sParam )
void SSCLoader::ProcessCombos( TimingData &out, const RString line, const int rowsPerBeat )
{
vector<RString> arrayComboExpressions;
split( sParam, ",", arrayComboExpressions );
split( line, ",", arrayComboExpressions );
for( unsigned f=0; f<arrayComboExpressions.size(); f++ )
{
vector<RString> arrayComboValues;
split( arrayComboExpressions[f], "=", arrayComboValues );
if( arrayComboValues.size() != 2 )
unsigned size = arrayComboValues.size();
if( size < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #COMBOS value \"%s\" (must have exactly one '='), ignored.",
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an invalid #COMBOS value \"%s\" (must have at least one '='), ignored.",
arrayComboExpressions[f].c_str() );
continue;
}
const float fComboBeat = StringToFloat( arrayComboValues[0] );
const int iCombos = StringToInt( arrayComboValues[1] );
ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos );
out.AddComboSegment( new_seg );
}
}
void SSCLoader::ProcessSpeeds( TimingData &out, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2[0] == 0 && vs2.size() == 2 ) // First one always seems to have 2.
{
vs2.push_back("0");
}
if( vs2.size() == 3 ) // use beats by default.
{
vs2.push_back("0");
}
if( vs2.size() < 4 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
SpeedSegment seg( fBeat, StringToFloat( vs2[1] ), StringToFloat( vs2[2] ));
seg.SetUnit(StringToInt(vs2[3]));
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f.", fBeat );
continue;
}
if( seg.GetLength() < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f, length %f.", fBeat, seg.GetLength() );
continue;
}
out.AddSpeedSegment( seg );
const int iMisses = (size == 2 ? iCombos : StringToInt(arrayComboValues[2]));
out.AddSegment( SEGMENT_COMBO, new ComboSegment( fComboBeat, iCombos, iMisses ) );
}
}
@@ -189,55 +122,120 @@ void SSCLoader::ProcessScrolls( TimingData &out, const RString sParam )
if( vs2.size() < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an scroll change with %i values.", (int)vs2.size() );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an scroll change with %i values.",
static_cast<int>(vs2.size()) );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
ScrollSegment seg( fBeat, StringToFloat( vs2[1] ) );
ScrollSegment * seg = new ScrollSegment(fBeat, StringToFloat( vs2[1] ) );
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an scroll change with beat %f.", fBeat );
LOG->UserLog("Song file",
this->GetSongTitle(),
"has an scroll change with beat %f.",
fBeat );
continue;
}
out.AddScrollSegment( seg );
out.AddSegment( SEGMENT_SCROLL, seg );
}
}
void SSCLoader::ProcessFakes( TimingData &out, const RString sParam )
bool SSCLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out )
{
vector<RString> arrayFakeExpressions;
split( sParam, ",", arrayFakeExpressions );
LOG->Trace( "Loading notes from %s", cachePath.c_str() );
for( unsigned b=0; b<arrayFakeExpressions.size(); b++ )
MsdFile msd;
if (!msd.ReadFile(cachePath, true))
{
vector<RString> arrayFakeValues;
split( arrayFakeExpressions[b], "=", arrayFakeValues );
// XXX: Hard to tell which file caused this.
if( arrayFakeValues.size() != 2 )
LOG->UserLog("Unable to load any notes from",
cachePath,
"for this reason: %s",
msd.GetError().c_str());
return false;
}
bool tryingSteps = false;
float storedVersion = 0;
const unsigned values = msd.GetNumValues();
for (unsigned i = 0; i < values; i++)
{
const MsdFile::value_t &params = msd.GetValue(i);
RString valueName = params[0];
valueName.MakeUpper();
RString matcher = params[1]; // mainly for debugging.
Trim(matcher);
if (valueName=="VERSION")
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #FAKES value \"%s\" (must have exactly one '='), ignored.",
arrayFakeExpressions[b].c_str() );
continue;
storedVersion = StringToFloat(matcher);
}
if (tryingSteps)
{
if( valueName=="STEPSTYPE" )
{
if (out.m_StepsType != GAMEMAN->StringToStepsType(matcher))
tryingSteps = false;
}
else if( valueName=="CHARTNAME")
{
if (storedVersion >= VERSION_CHART_NAME_TAG && out.GetChartName() != matcher)
tryingSteps = false;
}
else if( valueName=="DESCRIPTION" )
{
if (storedVersion < VERSION_CHART_NAME_TAG)
{
if (out.GetChartName() != matcher)
tryingSteps = false;
}
else if (out.GetDescription() != matcher)
tryingSteps = false;
}
else if( valueName=="DIFFICULTY" )
{
if (out.GetDifficulty() != StringToDifficulty(matcher))
tryingSteps = false;
}
else if( valueName=="METER" )
{
if (out.GetMeter() != StringToInt(matcher))
tryingSteps = false;
}
else if( valueName=="CREDIT" )
{
if (out.GetCredit() != matcher)
tryingSteps = false;
}
else if( valueName=="NOTES" || valueName=="NOTES2" )
{
out.SetSMNoteData(matcher);
out.TidyUpData();
return true;
}
}
const float fBeat = StringToFloat( arrayFakeValues[0] );
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 );
if(valueName == "NOTEDATA")
{
tryingSteps = true;
}
}
}
return false;
}
bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCache )
bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache )
{
LOG->Trace( "Song::LoadFromSSCFile(%s)", sPath.c_str() );
@@ -249,6 +247,7 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
}
out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
out.m_sSongFileName = sPath;
int state = GETTING_SONG_INFO;
const unsigned values = msd.GetNumValues();
@@ -274,6 +273,7 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
else if( sValueName=="TITLE" )
{
out.m_sMainTitle = sParams[1];
this->SetSongTitle(sParams[1]);
}
else if( sValueName=="SUBTITLE" )
@@ -355,7 +355,12 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
else if( sValueName=="LASTBEATHINT" )
{
out.m_fSpecifiedLastBeat = StringToFloat( sParams[1] );
// unable to parse due to tag position. Ignore.
}
else if (sValueName == "LASTSECONDHINT")
{
out.SetSpecifiedLastSecond(StringToFloat(sParams[1]));
}
else if( sValueName=="MUSICBYTES" )
@@ -421,7 +426,7 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
for( unsigned b=0; b<aFGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGSSCChangesString( change, aFGChangeExpressions[b] ) )
if( LoadFromBGChangesString( change, aFGChangeExpressions[b] ) )
out.AddForegroundChange( change );
}
}
@@ -434,7 +439,8 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
// Attacks loaded from file
else if( sValueName=="ATTACKS" )
{
SMLoader::ProcessAttacks( out, sParams );
ProcessAttackString(out.m_sAttackString, sParams);
ProcessAttacks(out.m_Attacks, sParams);
}
else if( sValueName=="OFFSET" )
@@ -484,16 +490,20 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
/* The following are cache tags. Never fill their values
* directly: only from the cached version. */
else if( sValueName=="FIRSTBEAT" )
else if( sValueName=="FIRSTBEAT" || sValueName=="LASTBEAT" )
{
// no longer used.
}
else if (sValueName=="FIRSTSECOND")
{
if( bFromCache )
out.m_fFirstBeat = StringToFloat( sParams[1] );
out.SetFirstSecond(StringToFloat(sParams[1]));
}
else if( sValueName=="LASTBEAT" )
else if( sValueName=="LASTSECOND" )
{
if( bFromCache )
out.m_fLastBeat = StringToFloat( sParams[1] );
out.SetLastSecond(StringToFloat(sParams[1]));
}
else if( sValueName=="SONGFILENAME" )
@@ -526,6 +536,10 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
}
case GETTING_STEP_INFO:
{
if (sValueName == "CHARTNAME")
{
pNewNotes->SetChartName(sParams[1]);
}
if( sValueName=="STEPSTYPE" )
{
pNewNotes->m_StepsType = GAMEMAN->StringToStepsType( sParams[1] );
@@ -538,7 +552,14 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
else if( sValueName=="DESCRIPTION" )
{
pNewNotes->SetDescription( sParams[1] );
if (out.m_fVersion < VERSION_CHART_NAME_TAG)
{
pNewNotes->SetChartName(sParams[1]);
}
else
{
pNewNotes->SetDescription(sParams[1]);
}
}
else if( sValueName=="DIFFICULTY" )
@@ -553,27 +574,35 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
else if( sValueName=="RADARVALUES" )
{
vector<RString> saValues;
split( sParams[1], ",", saValues, true );
int categories = NUM_RadarCategory;
if( out.m_fVersion < VERSION_RADAR_FAKE )
categories -= 1;
if( saValues.size() == (unsigned)categories * NUM_PLAYERS )
if (bFromCache)
{
RadarValues v[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
vector<RString> saValues;
split( sParams[1], ",", saValues, true );
int categories = NUM_RadarCategory;
if( out.m_fVersion < VERSION_RADAR_FAKE )
categories -= 1;
if( saValues.size() == (unsigned)categories * NUM_PLAYERS )
{
// Can't use the foreach anymore due to flexible radar lines.
for( RadarCategory rc = (RadarCategory)0; rc < categories;
enum_add<RadarCategory>( rc, +1 ) )
RadarValues v[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
{
v[pn][rc] = StringToFloat( saValues[pn*categories + rc] );
// 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] );
}
}
pNewNotes->SetCachedRadarValues( v );
}
pNewNotes->SetCachedRadarValues( v );
}
else
{
// just recalc at time.
}
}
else if( sValueName=="CREDIT" )
@@ -588,6 +617,7 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
pNewNotes->m_Timing = stepsTiming;
pNewNotes->SetSMNoteData( sParams[1] );
pNewNotes->TidyUpData();
pNewNotes->SetFilename(sPath);
out.AddSteps( pNewNotes );
}
@@ -649,13 +679,39 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
else if( sValueName=="ATTACKS" )
{
// Step Attacks aren't in yet.
ProcessAttackString(pNewNotes->m_sAttackString, sParams);
ProcessAttacks(pNewNotes->m_Attacks, sParams);
}
else if( sValueName=="OFFSET" )
{
stepsTiming.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="DISPLAYBPM" )
{
// #DISPLAYBPM:[xxx][xxx:xxx]|[*];
if( sParams[1] == "*" )
pNewNotes->SetDisplayBPM(DISPLAY_BPM_RANDOM);
else
{
pNewNotes->SetDisplayBPM(DISPLAY_BPM_SPECIFIED);
float min = StringToFloat(sParams[1]);
pNewNotes->SetMinBPM(min);
if(sParams[2].empty())
pNewNotes->SetMaxBPM(min);
else
pNewNotes->SetMaxBPM(StringToFloat(sParams[2]));
}
}
else if( sValueName=="STEPFILENAME" )
{
state = GETTING_SONG_INFO;
if( bHasOwnTiming )
pNewNotes->m_Timing = stepsTiming;
pNewNotes->SetFilename(sParams[1]);
out.AddSteps( pNewNotes );
}
break;
}
}
@@ -665,11 +721,6 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach
return true;
}
void SSCLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
{
GetDirListing( sPath + RString("*.ssc"), out );
}
bool SSCLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
LOG->Trace( "SSCLoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
@@ -677,21 +728,28 @@ bool SSCLoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool
int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath );
if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES )
{
LOG->UserLog( "Edit file", sEditFilePath, "is unreasonably large. It won't be loaded." );
LOG->UserLog("Edit file",
sEditFilePath,
"is unreasonably large. It won't be loaded." );
return false;
}
MsdFile msd;
if( !msd.ReadFile( sEditFilePath, true ) ) // unescape
{
LOG->UserLog( "Edit file", sEditFilePath, "couldn't be opened: %s", msd.GetError().c_str() );
LOG->UserLog("Edit file",
sEditFilePath,
"couldn't be opened: %s", msd.GetError().c_str() );
return false;
}
return LoadEditFromMsd( msd, sEditFilePath, slot, bAddStepsToSong );
}
bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
bool SSCLoader::LoadEditFromMsd(const MsdFile &msd,
const RString &sEditFilePath,
ProfileSlot slot,
bool bAddStepsToSong )
{
Song* pSong = NULL;
Steps* pNewNotes = NULL;
@@ -721,18 +779,25 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
}
RString sSongFullTitle = sParams[1];
this->SetSongTitle(sParams[1]);
sSongFullTitle.Replace( '\\', '/' );
pSong = SONGMAN->FindSong( sSongFullTitle );
if( pSong == NULL )
{
LOG->UserLog( "Edit file", sEditFilePath, "requires a song \"%s\" that isn't present.", sSongFullTitle.c_str() );
LOG->UserLog("Edit file",
sEditFilePath,
"requires a song \"%s\" that isn't present.",
sSongFullTitle.c_str() );
return false;
}
if( pSong->GetNumStepsLoadedFromProfile(slot) >= MAX_EDITS_PER_SONG_PER_PROFILE )
{
LOG->UserLog( "Song file", sSongFullTitle, "already has the maximum number of edits allowed for ProfileSlotP%d.", slot+1 );
LOG->UserLog("Song file",
sSongFullTitle,
"already has the maximum number of edits allowed for ProfileSlotP%d.",
slot+1 );
return false;
}
}
@@ -780,8 +845,8 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
{
RadarValues v[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
FOREACH_ENUM( RadarCategory, rc )
v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] );
FOREACH_ENUM( RadarCategory, rc )
v[pn][rc] = StringToFloat( saValues[pn*NUM_RadarCategory + rc] );
pNewNotes->SetCachedRadarValues( v );
}
bSSCFormat = true;
@@ -857,13 +922,18 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
{
if( pSong == NULL )
{
LOG->UserLog( "Edit file", sEditFilePath, "doesn't have a #SONG tag preceeding the first #NOTES tag." );
LOG->UserLog("Edit file",
sEditFilePath,
"doesn't have a #SONG tag preceeding the first #NOTES tag." );
return false;
}
if ( !bSSCFormat && iNumParams < 7 )
{
LOG->UserLog( "Edit file", sEditFilePath, "has %d fields in a #NOTES tag, but should have at least 7.", iNumParams );
LOG->UserLog("Edit file",
sEditFilePath,
"has %d fields in a #NOTES tag, but should have at least 7.",
iNumParams );
continue;
}
@@ -883,9 +953,13 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
else
{
pNewNotes = pSong->CreateSteps();
SMLoader::LoadFromSMTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
LoadFromTokens(sParams[1],
sParams[2],
sParams[3],
sParams[4],
sParams[5],
sParams[6],
*pNewNotes);
}
pNewNotes->SetLoadedFromProfile( slot );
@@ -894,7 +968,9 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
if( pSong->IsEditAlreadyLoaded(pNewNotes) )
{
LOG->UserLog( "Edit file", sEditFilePath, "is a duplicate of another edit that was already loaded." );
LOG->UserLog("Edit file",
sEditFilePath,
"is a duplicate of another edit that was already loaded." );
SAFE_DELETE( pNewNotes );
return false;
}
@@ -904,7 +980,10 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
}
else
{
LOG->UserLog( "Edit file", sEditFilePath, "has an unexpected value \"%s\".", sValueName.c_str() );
LOG->UserLog("Edit file",
sEditFilePath,
"has an unexpected value \"%s\".",
sValueName.c_str() );
}
}
@@ -913,11 +992,6 @@ bool SSCLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
return bSSCFormat;
}
void SSCLoader::TidyUpData( Song &song, bool bFromCache )
{
SMLoader::TidyUpData(song, bFromCache);
}
/*
* (c) 2011 Jason Felds
* All rights reserved.
+21 -25
View File
@@ -3,6 +3,7 @@
#define NotesLoaderSSC_H
#include "GameConstantsAndTypes.h"
#include "NotesLoaderSM.h"
class MsdFile;
class Song;
@@ -25,19 +26,20 @@ const float VERSION_RADAR_FAKE = 0.53f;
const float VERSION_WARP_SEGMENT = 0.56f;
/** @brief The version that formally introduced Split Timing. */
const float VERSION_SPLIT_TIMING = 0.7f;
/** @brief The version that moved the step's Offset higher up. */
const float VERSION_OFFSET_BEFORE_ATTACK = 0.72f;
/** @brief The version that introduced the Chart Name tag. */
const float VERSION_CHART_NAME_TAG = 0.74f;
/** @brief The version that introduced the cache switch tag. */
const float VERSION_CACHE_SWITCH_TAG = 0.77f;
/**
* @brief The SSCLoader handles all of the parsing needed for .ssc files.
*/
namespace SSCLoader
struct SSCLoader : public SMLoader
{
/**
* @brief Attempt to load a song from a specified path.
* @param sPath a const reference to the path on the hard drive to check.
* @param out a reference to the Song that will retrieve the song information.
* @return its success or failure.
*/
bool LoadFromDir( const RString &sPath, Song &out );
SSCLoader() : SMLoader(".ssc") {}
/**
* @brief Attempt to load the specified ssc file.
* @param sPath a const reference to the path on the hard drive to check.
@@ -45,13 +47,8 @@ namespace SSCLoader
* @param bFromCache a check to see if we are getting certain information from the cache file.
* @return its success or failure.
*/
bool LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCache = false );
/**
* @brief Retrieve the list of .ssc files.
* @param sPath a const reference to the path on the hard drive to check.
* @param out a vector of files found in the path.
*/
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
virtual bool LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache = false );
/**
* @brief Attempt to load an edit from the hard drive.
* @param sEditFilePath a path on the hard drive to check.
@@ -69,21 +66,20 @@ namespace SSCLoader
* @return its success or failure.
*/
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
/**
* @brief Perform some cleanup on the loaded song.
* @param song a reference to the song that may need cleaning up.
* @param bFromCache a flag to determine if this song is loaded from a cache file.
*/
void TidyUpData( Song &song, bool bFromCache );
/**
* @brief Retrieve the specific NoteData from the file.
* @param cachePath the path to the cache file.
* @param out the Steps to receive just the particular notedata.
* @return true if successful, false otherwise. */
virtual bool LoadNoteDataFromSimfile( const RString &cachePath, Steps &out );
void ProcessWarps( TimingData &, const RString, const float );
void ProcessLabels( TimingData &, const RString );
void ProcessCombos( TimingData &, const RString );
void ProcessSpeeds( TimingData &, const RString );
virtual void ProcessCombos( TimingData &, const RString, const int = -1 );
void ProcessScrolls( TimingData &, const RString );
void ProcessFakes( TimingData &, const RString );
}
};
#endif
/**
* @file
+20 -13
View File
@@ -11,6 +11,8 @@
#include "Song.h"
#include "Steps.h"
RString OptimizeDWIString( RString holds, RString taps );
/**
* @brief Optimize an individual pair of characters whenever possible.
* @param c1 the first character.
@@ -350,9 +352,13 @@ 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_SongTiming.m_BPMSegments[0].GetRow() == 0 );
const vector<TimingSegment *> &bpms = out.m_SongTiming.allTimingSegments[SEGMENT_BPM];
ASSERT_M(bpms[0]->GetRow() == 0,
ssprintf("The first BPM Segment must be defined at row 0, not %d!", bpms[0]->GetRow()) );
f.PutLine( ssprintf("#FILE:%s;", DwiEscape(out.m_sMusicFile).c_str()) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_SongTiming.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#BPM:%.3f;", static_cast<BPMSegment *>(bpms[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) );
@@ -374,29 +380,30 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
break;
}
if( !out.m_SongTiming.m_StopSegments.empty() )
const vector<TimingSegment *> &stops = out.m_SongTiming.allTimingSegments[SEGMENT_STOP_DELAY];
if( !stops.empty() )
{
f.Write( "#FREEZE:" );
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
for( unsigned i=0; i<stops.size(); i++ )
{
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
f.Write( ssprintf("%.3f=%.3f", fs.GetRow() * 4.0f / ROWS_PER_BEAT,
roundf(fs.GetPause()*1000)) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
const StopSegment *fs = static_cast<StopSegment *>(stops[i]);
f.Write( ssprintf("%.3f=%.3f", fs->GetRow() * 4.0f / ROWS_PER_BEAT,
roundf(fs->GetPause()*1000)) );
if( i != stops.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
}
if( out.m_SongTiming.m_BPMSegments.size() > 1)
if( bpms.size() > 1)
{
f.Write( "#CHANGEBPM:" );
for( unsigned i=1; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
for( unsigned i=1; i<bpms.size(); i++ )
{
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.Write( ssprintf("%.3f=%.3f", bs.GetRow() * 4.0f / ROWS_PER_BEAT, bs.GetBPM() ) );
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
const BPMSegment *bs = static_cast<BPMSegment *>(bpms[i]);
f.Write( ssprintf("%.3f=%.3f", bs->GetRow() * 4.0f / ROWS_PER_BEAT, bs->GetBPM() ) );
if( i != bpms.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
+13 -12
View File
@@ -9,22 +9,23 @@
#include "NoteData.h"
#include "GameManager.h"
void Serialize(const BPMSegment &seg, Json::Value &root)
static void Serialize(const TimingSegment &seg, Json::Value &root)
{
root["Beat"] = seg.GetBeat();
root["BPM"] = seg.GetBPM();
}
static void Serialize(const StopSegment &seg, Json::Value &root)
{
root["Beat"] = seg.GetBeat();
root["Seconds"] = seg.GetPause();
if (seg.GetType() == SEGMENT_BPM)
{
root["BPM"] = static_cast<BPMSegment &>(const_cast<TimingSegment &>(seg)).GetBPM();
}
else
{
root["Seconds"] = static_cast<StopSegment &>(const_cast<TimingSegment &>(seg)).GetPause();
}
}
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"] );
JsonUtil::SerializeVectorPointers( td.allTimingSegments[SEGMENT_BPM], Serialize, root["BpmSegments"] );
JsonUtil::SerializeVectorPointers( td.allTimingSegments[SEGMENT_STOP_DELAY], Serialize, root["StopSegments"] );
}
static void Serialize(const LyricSegment &o, Json::Value &root)
@@ -147,8 +148,8 @@ bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWr
else
root["Selectable"] = "YES";
root["FirstBeat"] = out.m_fFirstBeat;
root["LastBeat"] = out.m_fLastBeat;
root["FirstBeat"] = out.GetFirstBeat();
root["LastBeat"] = out.GetLastBeat();
root["SongFileName"] = out.m_sSongFileName;
root["HasMusic"] = out.m_bHasMusic;
root["HasBanner"] = out.m_bHasBanner;
+29 -59
View File
@@ -19,37 +19,13 @@
ThemeMetric<bool> USE_CREDIT ( "NotesWriterSM", "DescriptionUsesCreditField" );
/**
* @brief Turn the BackgroundChange into a string.
* @param bgc the BackgroundChange in question.
* @return the converted string. */
static RString BackgroundChangeToString( const BackgroundChange &bgc )
{
// TODO: Technically we need to double-escape the filename (because it might
// contain '=') and then unescape the value returned by the MsdFile.
RString s = ssprintf(
"%.3f=%s=%.3f=%d=%d=%d=%s=%s=%s=%s=%s",
bgc.m_fStartBeat,
SmEscape(bgc.m_def.m_sFile1).c_str(),
bgc.m_fRate,
bgc.m_sTransition == SBT_CrossFade, // backward compat
bgc.m_def.m_sEffect == SBE_StretchRewind, // backward compat
bgc.m_def.m_sEffect != SBE_StretchNoLoop, // backward compat
bgc.m_def.m_sEffect.c_str(),
bgc.m_def.m_sFile2.c_str(),
bgc.m_sTransition.c_str(),
SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor1)).c_str(),
SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor2)).c_str()
);
return s;
}
/**
* @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, Song &out )
{
TimingData &timing = out.m_SongTiming;
f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) );
f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) );
f.PutLine( ssprintf( "#ARTIST:%s;", SmEscape(out.m_sArtist).c_str() ) );
@@ -66,8 +42,9 @@ static void WriteGlobalTags( RageFile &f, Song &out )
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 )
f.PutLine( ssprintf("#LASTBEATHINT:%.3f;", out.m_fSpecifiedLastBeat) );
float specBeat = out.GetSpecifiedLastBeat();
if( specBeat > 0 )
f.PutLine( ssprintf("#LASTBEATHINT:%.3f;", specBeat) );
f.Write( "#SELECTABLE:" );
switch(out.m_SelectionDisplay)
@@ -98,44 +75,46 @@ static void WriteGlobalTags( RageFile &f, Song &out )
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
vector<TimingSegment *> &bpms = timing.allTimingSegments[SEGMENT_BPM];
for( unsigned i=0; i<bpms.size(); i++ )
{
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
const BPMSegment *bs = static_cast<BPMSegment *>(bpms[i]);
f.PutLine( ssprintf( "%.3f=%.3f", bs.GetBeat(), bs.GetBPM() ) );
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.PutLine( ssprintf( "%.3f=%.3f", bs->GetBeat(), bs->GetBPM() ) );
if( i != bpms.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
unsigned wSize = out.m_SongTiming.m_WarpSegments.size();
vector<TimingSegment *> &warps = timing.allTimingSegments[SEGMENT_WARP];
unsigned wSize = warps.size();
if( wSize > 0 )
{
for( unsigned i=0; i < wSize; i++ )
{
int iRow = out.m_SongTiming.m_WarpSegments[i].GetRow();
const WarpSegment *ws = static_cast<WarpSegment *>(warps[i]);
int iRow = ws->GetRow();
float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow);
float fSkip = fBPS * out.m_SongTiming.m_WarpSegments[i].GetLength();
StopSegment ss(iRow, -fSkip, false);
out.m_SongTiming.AddStopSegment( ss );
float fSkip = fBPS * ws->GetLength();
out.m_SongTiming.AddSegment(SEGMENT_STOP_DELAY,
new StopSegment(iRow, -fSkip, false) );
}
}
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
vector<TimingSegment *> &stops = timing.allTimingSegments[SEGMENT_STOP_DELAY];
for( unsigned i=0; i<stops.size(); i++ )
{
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
float fBeat = fs.GetBeat();
if (fs.GetDelay()) fBeat--;
const StopSegment *fs = static_cast<StopSegment *>(stops[i]);
float fBeat = fs->GetBeat();
if (fs->GetDelay()) fBeat--;
f.PutLine( ssprintf( "%.3f=%.3f", fBeat, fs.GetPause() ) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.PutLine( ssprintf( "%.3f=%.3f", fBeat, fs->GetPause() ) );
if( i != stops.size()-1 )
f.Write( "," );
if( fs.GetPause() < 0 )
if( fs->GetPause() < 0 )
{
out.m_SongTiming.m_StopSegments.erase(
out.m_SongTiming.m_StopSegments.begin()+i,
out.m_SongTiming.m_StopSegments.begin()+i+1 );
stops.erase(stops.begin()+i,stops.begin()+i+1 );
i--;
}
}
@@ -151,7 +130,7 @@ static void WriteGlobalTags( RageFile &f, Song &out )
f.Write( ssprintf("#BGCHANGES%d:", b+1) );
FOREACH_CONST( BackgroundChange, out.GetBackgroundChanges(b), bgc )
f.PutLine( BackgroundChangeToString(*bgc)+"," );
f.PutLine( (*bgc).ToString() +"," );
/* If there's an animation plan at all, add a dummy "-nosongbg-" tag to indicate that
* this file doesn't want a song BG entry added at the end. See SMLoader::TidyUpData.
@@ -167,7 +146,7 @@ static void WriteGlobalTags( RageFile &f, Song &out )
f.Write( "#FGCHANGES:" );
FOREACH_CONST( BackgroundChange, out.GetForegroundChanges(), bgc )
{
f.PutLine( BackgroundChangeToString(*bgc)+"," );
f.PutLine( (*bgc).ToString() +"," );
}
f.PutLine( ";" );
}
@@ -181,16 +160,7 @@ static void WriteGlobalTags( RageFile &f, Song &out )
}
f.PutLine( ";" );
f.Write( "#ATTACKS:" );
for( unsigned a=0; a < out.m_sAttackString.size(); a++ )
{
RString sData = out.m_sAttackString[a];
f.Write( ssprintf( "%s", sData.c_str() ) );
if( a != (out.m_sAttackString.size() - 1) )
f.Write( ":" ); // Not the end, so write a divider ':'
}
f.PutLine( ";" );
f.PutLine( ssprintf("#ATTACKS:%s;", out.GetAttackString().c_str()) );
}
/**
@@ -225,7 +195,7 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) );
lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" );
lines.push_back( ssprintf( " %s:", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) );
RString desc = (USE_CREDIT ? in.GetCredit() : in.GetDescription());
RString desc = (USE_CREDIT ? in.GetCredit() : in.GetChartName());
lines.push_back( ssprintf( " %s:", SmEscape(desc).c_str() ) );
lines.push_back( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) );
lines.push_back( ssprintf( " %d:", in.GetMeter() ) );
+134 -81
View File
@@ -16,31 +16,6 @@
#include "Song.h"
#include "Steps.h"
/**
* @brief Turn the BackgroundChange into a string.
* @param bgc the BackgroundChange in question.
* @return the converted string. */
static RString BackgroundChangeToString( const BackgroundChange &bgc )
{
// TODO: Technically we need to double-escape the filename (because it might contain '=') and then
// unescape the value returned by the MsdFile.
RString s = ssprintf(
"%.3f=%s=%.3f=%d=%d=%d=%s=%s=%s=%s=%s",
bgc.m_fStartBeat,
SmEscape(bgc.m_def.m_sFile1).c_str(),
bgc.m_fRate,
bgc.m_sTransition == SBT_CrossFade, // backward compat
bgc.m_def.m_sEffect == SBE_StretchRewind, // backward compat
bgc.m_def.m_sEffect != SBE_StretchNoLoop, // backward compat
bgc.m_def.m_sEffect.c_str(),
bgc.m_def.m_sFile2.c_str(),
bgc.m_sTransition.c_str(),
SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor1)).c_str(),
SmEscape(RageColor::NormalizeColorString(bgc.m_def.m_sColor2)).c_str()
);
return s;
}
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
@@ -90,80 +65,144 @@ static void GetTimingTags( vector<RString> &lines, TimingData timing, bool bIsSo
TimingTagWriter w ( &lines );
timing.TidyUpData();
unsigned i = 0;
w.Init( "BPMS" );
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, bs )
vector<TimingSegment *> &bpms = timing.allTimingSegments[SEGMENT_BPM];
for (; i < bpms.size(); i++)
{
BPMSegment *bs = static_cast<BPMSegment *>(bpms[i]);
w.Write( bs->GetRow(), bs->GetBPM() );
}
w.Finish();
w.Init( "STOPS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
vector<TimingSegment *> &stops = timing.allTimingSegments[SEGMENT_STOP_DELAY];
for (i = 0; i < stops.size(); i++)
{
StopSegment *ss = static_cast<StopSegment *>(stops[i]);
if( !ss->GetDelay() )
w.Write( ss->GetRow(), ss->GetPause() );
}
w.Finish();
w.Init( "DELAYS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
for (i = 0; i < stops.size(); i++)
{
StopSegment *ss = static_cast<StopSegment *>(stops[i]);
if( ss->GetDelay() )
w.Write( ss->GetRow(), ss->GetPause() );
}
w.Finish();
w.Init( "WARPS" );
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, ws )
vector<TimingSegment *> &warps = timing.allTimingSegments[SEGMENT_WARP];
for (i = 0; i < warps.size(); i++)
{
WarpSegment *ws = static_cast<WarpSegment *>(warps[i]);
w.Write( ws->GetRow(), ws->GetLength() );
}
w.Finish();
ASSERT( !timing.m_vTimeSignatureSegments.empty() );
vector<TimingSegment *> &tSigs = timing.allTimingSegments[SEGMENT_TIME_SIG];
ASSERT( !tSigs.empty() );
w.Init( "TIMESIGNATURES" );
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
w.Write( iter->GetRow(), iter->GetNum(), iter->GetDen() );
for (i = 0; i < tSigs.size(); i++)
{
TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]);
w.Write( ts->GetRow(), ts->GetNum(), ts->GetDen() );
}
w.Finish();
ASSERT( !timing.m_TickcountSegments.empty() );
vector<TimingSegment *> &ticks = timing.allTimingSegments[SEGMENT_TICKCOUNT];
ASSERT( !ticks.empty() );
w.Init( "TICKCOUNTS" );
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, ts )
for (i = 0; i < ticks.size(); i++)
{
TickcountSegment *ts = static_cast<TickcountSegment *>(ticks[i]);
w.Write( ts->GetRow(), ts->GetTicks() );
}
w.Finish();
ASSERT( !timing.m_ComboSegments.empty() );
vector<TimingSegment *> &combos = timing.allTimingSegments[SEGMENT_COMBO];
ASSERT( !combos.empty() );
w.Init( "COMBOS" );
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, cs )
w.Write( cs->GetRow(), cs->GetCombo() );
for (i = 0; i < combos.size(); i++)
{
ComboSegment *cs = static_cast<ComboSegment *>(combos[i]);
if (cs->GetCombo() == cs->GetMissCombo())
w.Write( cs->GetRow(), cs->GetCombo() );
else
w.Write( cs->GetRow(), cs->GetCombo(), cs->GetMissCombo() );
}
w.Finish();
// Song Timing should only have the initial value.
vector<TimingSegment *> &speeds = timing.allTimingSegments[SEGMENT_SPEED];
w.Init( "SPEEDS" );
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, ss )
for (i = 0; i < speeds.size(); i++)
{
SpeedSegment *ss = static_cast<SpeedSegment *>(speeds[i]);
w.Write( ss->GetRow(), ss->GetRatio(), ss->GetLength(), ss->GetUnit() );
}
w.Finish();
w.Init( "SCROLLS" );
FOREACH_CONST( ScrollSegment, timing.m_ScrollSegments, ss )
vector<TimingSegment *> &scrolls = timing.allTimingSegments[SEGMENT_SCROLL];
for (i = 0; i < scrolls.size(); i++)
{
ScrollSegment *ss = static_cast<ScrollSegment *>(scrolls[i]);
w.Write( ss->GetRow(), ss->GetRatio() );
}
w.Finish();
if( !bIsSong )
{
vector<TimingSegment *> &fakes = timing.allTimingSegments[SEGMENT_FAKE];
w.Init( "FAKES" );
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, fs )
for (i = 0; i < fakes.size(); i++)
{
FakeSegment *fs = static_cast<FakeSegment *>(fakes[i]);
w.Write( fs->GetRow(), fs->GetLength() );
}
w.Finish();
}
w.Init( "LABELS" );
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, ls )
vector<TimingSegment *> &labels = timing.allTimingSegments[SEGMENT_LABEL];
for (i = 0; i < labels.size(); i++)
{
LabelSegment *ls = static_cast<LabelSegment *>(labels[i]);
w.Write( ls->GetRow(), ls->GetLabel().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 ) );
f.PutLine(ssprintf("#BPMS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_BPM)).c_str()));
f.PutLine(ssprintf("#STOPS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_STOP_DELAY, false)).c_str()));
f.PutLine(ssprintf("#DELAYS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_STOP_DELAY, true)).c_str()));
f.PutLine(ssprintf("#WARPS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_WARP)).c_str()));
f.PutLine(ssprintf("#TIMESIGNATURES:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_TIME_SIG)).c_str()));
f.PutLine(ssprintf("#TICKCOUNTS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_TICKCOUNT)).c_str()));
f.PutLine(ssprintf("#COMBOS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_COMBO)).c_str()));
f.PutLine(ssprintf("#SPEEDS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_SPEED)).c_str()));
f.PutLine(ssprintf("#SCROLLS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_SCROLL)).c_str()));
if (!bIsSong)
f.PutLine(ssprintf("#FAKES:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_FAKE)).c_str()));
f.PutLine(ssprintf("#LABELS:%s;",
join(",\r\n", timing.ToVectorString(SEGMENT_LABEL)).c_str()));
}
@@ -190,10 +229,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
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] );
vector<RString> vs = out.GetInstrumentTracksToVectorString();
if( !vs.empty() )
{
RString s = join( ",", vs );
@@ -203,13 +239,11 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
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 )
f.PutLine( ssprintf("#LASTBEATHINT:%.6f;", out.m_fSpecifiedLastBeat) );
f.Write( "#SELECTABLE:" );
switch(out.m_SelectionDisplay)
{
default: ASSERT(0); // fall through
default: ASSERT_M(0, "An invalid selectable value was found for this song!"); // fall through
case Song::SHOW_ALWAYS: f.Write( "YES" ); break;
//case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break;
case Song::SHOW_NEVER: f.Write( "NO" ); break;
@@ -234,6 +268,9 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
WriteTimingTags( f, out.m_SongTiming, true );
if( out.GetSpecifiedLastSecond() > 0 )
f.PutLine( ssprintf("#LASTSECONDHINT:%.6f;", out.GetSpecifiedLastSecond()) );
FOREACH_BackgroundLayer( b )
{
if( b==0 )
@@ -244,7 +281,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.Write( ssprintf("#BGCHANGES%d:", b+1) );
FOREACH_CONST( BackgroundChange, out.GetBackgroundChanges(b), bgc )
f.PutLine( BackgroundChangeToString(*bgc)+"," );
f.PutLine( (*bgc).ToString() +"," );
/* If there's an animation plan at all, add a dummy "-nosongbg-" tag to
* indicate that this file doesn't want a song BG entry added at the end.
@@ -260,7 +297,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.Write( "#FGCHANGES:" );
FOREACH_CONST( BackgroundChange, out.GetForegroundChanges(), bgc )
{
f.PutLine( BackgroundChangeToString(*bgc)+"," );
f.PutLine( (*bgc).ToString() +"," );
}
f.PutLine( ";" );
}
@@ -274,16 +311,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
}
f.PutLine( ";" );
f.Write( "#ATTACKS:" );
for( unsigned a=0; a < out.m_sAttackString.size(); a++ )
{
RString sData = out.m_sAttackString[a];
f.Write( ssprintf( "%s", sData.c_str() ) );
if( a != (out.m_sAttackString.size() - 1) )
f.Write( ":" ); // Not the end, so write a divider ':'
}
f.PutLine( ";" );
f.PutLine( ssprintf("#ATTACKS:%s;", out.GetAttackString().c_str()) );
}
/**
@@ -301,6 +329,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf("//---------------%s - %s----------------",
GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) );
lines.push_back( "#NOTEDATA:;" ); // our new separator.
lines.push_back( ssprintf( "#CHARTNAME:%s;", SmEscape(in.GetChartName()).c_str()));
lines.push_back( ssprintf( "#STEPSTYPE:%s;", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) );
lines.push_back( ssprintf( "#DESCRIPTION:%s;", SmEscape(in.GetDescription()).c_str() ) );
lines.push_back( ssprintf( "#CHARTSTYLE:%s;", SmEscape(in.GetChartStyle()).c_str() ) );
@@ -317,22 +346,46 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#RADARVALUES:%s;", join(",",asRadarValues).c_str() ) );
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
GetTimingTags( lines, in.m_Timing );
// For now, attacks are NOT in use for the step.
lines.push_back( "#ATTACKS:;" );
lines.push_back( ssprintf( "#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds ) );
RString sNoteData;
in.GetSMNoteData( sNoteData );
GetTimingTags( lines, in.m_Timing );
lines.push_back( ssprintf("#ATTACKS:%s;", in.GetAttackString().c_str()));
switch( in.GetDisplayBPM() )
{
case DISPLAY_BPM_ACTUAL:
// write nothing
break;
case DISPLAY_BPM_SPECIFIED:
{
float small = in.GetMinBPM();
float big = in.GetMaxBPM();
if (small == big)
lines.push_back( ssprintf( "#DISPLAYBPM:%.6f;", small ) );
else
lines.push_back( ssprintf( "#DISPLAYBPM:%.6f:%.6f;", small, big ) );
break;
}
case DISPLAY_BPM_RANDOM:
lines.push_back( ssprintf( "#DISPLAYBPM:*;" ) );
break;
}
if (bSavingCache)
{
lines.push_back(ssprintf("#STEPFILENAME:%s;", in.GetFilename().c_str()));
}
else
{
RString sNoteData;
in.GetSMNoteData( sNoteData );
lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" );
TrimLeft(sNoteData);
split( sNoteData, "\n", lines, true );
lines.push_back( ";" );
lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" );
TrimLeft(sNoteData);
split( sNoteData, "\n", lines, true );
lines.push_back( ";" );
}
return JoinLineList( lines );
}
@@ -358,12 +411,12 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
if( bSavingCache )
{
f.PutLine( ssprintf( "// cache tags:" ) );
f.PutLine( ssprintf( "#FIRSTBEAT:%.3f;", out.m_fFirstBeat ) );
f.PutLine( ssprintf( "#LASTBEAT:%.3f;", out.m_fLastBeat ) );
f.PutLine( ssprintf( "#FIRSTSECOND:%.6f;", out.GetFirstSecond() ) );
f.PutLine( ssprintf( "#LASTSECOND:%.6f;", out.GetLastSecond() ) );
f.PutLine( ssprintf( "#SONGFILENAME:%s;", out.m_sSongFileName.c_str() ) );
f.PutLine( ssprintf( "#HASMUSIC:%i;", out.m_bHasMusic ) );
f.PutLine( ssprintf( "#HASBANNER:%i;", out.m_bHasBanner ) );
f.PutLine( ssprintf( "#MUSICLENGTH:%.3f;", out.m_fMusicLengthSeconds ) );
f.PutLine( ssprintf( "#MUSICLENGTH:%.6f;", out.m_fMusicLengthSeconds ) );
f.PutLine( ssprintf( "// end cache tags" ) );
}
+3 -3
View File
@@ -196,7 +196,7 @@ RString OptionRow::GetRowTitle() const
if( GAMESTATE->m_pCurCourse )
{
const Trail* pTrail = GAMESTATE->m_pCurTrail[GAMESTATE->m_MasterPlayerNumber];
const Trail* pTrail = GAMESTATE->m_pCurTrail[GAMESTATE->GetMasterPlayerNumber()];
ASSERT( pTrail != NULL );
const int iNumCourseEntries = pTrail->m_vEntries.size();
if( iNumCourseEntries > CommonMetrics::MAX_COURSE_ENTRIES_BEFORE_VARIOUS )
@@ -417,8 +417,8 @@ void OptionRow::AfterImportOptions( PlayerNumber pn )
// we need to copy p2 to p1, not p1 to p2.
if( m_pHand->m_Def.m_bOneChoiceForAllPlayers )
{
PlayerNumber pnCopyFrom = GAMESTATE->m_MasterPlayerNumber;
if( GAMESTATE->m_MasterPlayerNumber == PLAYER_INVALID )
PlayerNumber pnCopyFrom = GAMESTATE->GetMasterPlayerNumber();
if( GAMESTATE->GetMasterPlayerNumber() == PLAYER_INVALID )
pnCopyFrom = PLAYER_1;
FOREACH_PlayerNumber( p )
m_vbSelected[p] = m_vbSelected[pnCopyFrom];
+3
View File
@@ -14,6 +14,9 @@ class OptionRowHandler;
class GameCommand;
struct OptionRowDefinition;
RString ITEMS_LONG_ROW_X_NAME( size_t p );
RString MOD_ICON_X_NAME( size_t p );
class OptionRowType
{
public:
+19 -2
View File
@@ -28,6 +28,7 @@
#define ENTRY_MODE(s,i) THEME->GetMetric ("ScreenOptionsMaster",ssprintf("%s,%i",(s).c_str(),(i+1)))
#define ENTRY_DEFAULT(s) THEME->GetMetric ("ScreenOptionsMaster",(s) + "Default")
#define NOTE_SKIN_SORT_ORDER THEME->GetMetric ("ScreenOptionsMaster","NoteSkinSortOrder")
#define STEPS_USE_CHART_NAME THEME->GetMetricB("ScreenOptionsMaster","StepsUseChartName")
static const char *SelectTypeNames[] = {
"SelectOne",
@@ -449,17 +450,33 @@ class OptionRowHandlerListSteps : public OptionRowHandlerList
Steps* pSteps = vpSteps[i];
RString s;
if (STEPS_USE_CHART_NAME)
{
s = pSteps->GetChartName();
// TODO: find a way to make this use lua or metrics.
if (!(s == "" || s == "blank" || s == "Blank"))
{
goto nameGotten;
}
}
if( pSteps->GetDifficulty() == Difficulty_Edit )
{
s = pSteps->GetDescription();
s = pSteps->GetChartName();
if (s == "" || s == "blank" || s == "Blank")
s = pSteps->GetDescription();
}
else
{
if( pSteps->IsAnEdit() )
s = pSteps->GetDescription();
{
s = pSteps->GetChartName();
if (s == "" || s == "blank" || s == "Blank")
s = pSteps->GetDescription();
}
else
s = CustomDifficultyToLocalizedString( GetCustomDifficulty( pSteps->m_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) );
}
nameGotten:
s += ssprintf( " %d", pSteps->GetMeter() );
m_Def.m_vsChoices.push_back( s );
GameCommand mc;
-20
View File
@@ -137,26 +137,6 @@ void PercentageDisplay::Refresh()
else
{
float fPercentDancePoints = m_pPlayerStageStats->GetPercentDancePoints();
float fCurMaxPercentDancePoints = m_pPlayerStageStats->GetCurMaxPercentDancePoints();
if( m_bApplyScoreDisplayOptions )
{
switch( m_pPlayerState->m_PlayerOptions.GetCurrent().m_ScoreDisplay )
{
case PlayerOptions::SCORING_ADD:
// nothing to do
break;
case PlayerOptions::SCORING_SUBTRACT:
fPercentDancePoints = 1.0f - ( fCurMaxPercentDancePoints - fPercentDancePoints );
break;
case PlayerOptions::SCORING_AVERAGE:
if( fCurMaxPercentDancePoints == 0.0f ) // don't divide by zero fats
fPercentDancePoints = 0.0f;
else
fPercentDancePoints = fPercentDancePoints / fCurMaxPercentDancePoints;
break;
}
}
// clamp percentage - feedback is that negative numbers look weird here.
CLAMP( fPercentDancePoints, 0.f, 1.f );
+158 -45
View File
@@ -42,6 +42,9 @@
#include "LocalizedString.h"
#include "AdjustSync.h"
RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides );
void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut );
/**
* @brief Helper class to ensure that each row is only judged once without taking too much memory.
*/
@@ -112,7 +115,7 @@ void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, floa
case TW_W5: defaultValueOut = 0.180f; break;
case TW_Mine: defaultValueOut = 0.090f; break; // same as great
case TW_Hold: defaultValueOut = 0.500f; break; // allow enough time to take foot off and put back on
case TW_Roll: defaultValueOut = 0.350f; break;
case TW_Roll: defaultValueOut = 0.500f; break;
case TW_Attack: defaultValueOut = 0.135f; break;
}
}
@@ -198,6 +201,12 @@ ThemeMetric<int> COMBO_STOPPED_AT ( "Player", "ComboStoppedAt" );
ThemeMetric<float> ATTACK_RUN_TIME_RANDOM ( "Player", "AttackRunTimeRandom" );
ThemeMetric<float> ATTACK_RUN_TIME_MINE ( "Player", "AttackRunTimeMine" );
/**
* @brief What is our highest cap for mMods?
*
* If set to 0 or less, assume the song takes over. */
ThemeMetric<float> M_MOD_HIGH_CAP("Player", "MModHighCap");
/** @brief Will battle modes have their steps mirrored or kept the same? */
ThemeMetric<bool> BATTLE_RAVE_MIRROR ( "Player", "BattleRaveMirror" );
@@ -398,6 +407,75 @@ void Player::Init(
break;
}
// calculate M-mod speed here, so we can adjust properly on a per-song basis.
// XXX: can we find a better location for this?
if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fMaxScrollBPM != 0 )
{
DisplayBpms bpms;
if( GAMESTATE->IsCourseMode() )
{
ASSERT( GAMESTATE->m_pCurTrail[pn] );
GAMESTATE->m_pCurTrail[pn]->GetDisplayBpms( bpms );
}
else
{
ASSERT( GAMESTATE->m_pCurSong );
GAMESTATE->m_pCurSong->GetDisplayBpms( bpms );
}
float fMaxBPM = 0;
/* TODO: Find a way to not go above a certain BPM range
* for getting the max BPM. Otherwise, you get songs
* like Tsuhsuixamush, M550, 0.18x speed. Even slow
* speed readers would not generally find this fun.
* -Wolfman2000
*/
// all BPMs are listed and available, so try them first.
// get the maximum listed value for the song or course.
// if the BPMs are < 0, reset and get the actual values.
if( !bpms.IsSecret() )
{
fMaxBPM = (M_MOD_HIGH_CAP > 0 ?
bpms.GetMaxWithin(M_MOD_HIGH_CAP) :
bpms.GetMax());
fMaxBPM = max( 0, fMaxBPM );
}
// we can't rely on the displayed BPMs, so manually calculate.
if( fMaxBPM == 0 )
{
float fThrowAway = 0;
if( GAMESTATE->IsCourseMode() )
{
FOREACH_CONST( TrailEntry, GAMESTATE->m_pCurTrail[pn]->m_vEntries, e )
{
float fMaxForEntry;
if (M_MOD_HIGH_CAP > 0)
e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry, M_MOD_HIGH_CAP );
else
e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry );
fMaxBPM = max( fMaxForEntry, fMaxBPM );
}
}
else
{
if (M_MOD_HIGH_CAP > 0)
GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM, M_MOD_HIGH_CAP );
else
GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM );
}
}
ASSERT( fMaxBPM > 0 );
// set an X-mod equal to Mnum / fMaxBPM (e.g. M600 with 150 becomes 4x)
PO_GROUP_ASSIGN(m_pPlayerState->m_PlayerOptions, ModsLevel_Preferred, m_fScrollSpeed,
m_pPlayerState->m_PlayerOptions.GetPreferred().m_fMaxScrollBPM / fMaxBPM);
}
float fBalance = GameSoundManager::GetPlayerBalance( pn );
m_soundMine.SetProperty( "Pan", fBalance );
@@ -789,7 +867,7 @@ void Player::Update( float fDeltaTime )
Actor::TweenState::MakeWeightedAverage( m_pActorWithComboPosition->DestTweenState(), ts1, ts2, fPercentCentered );
}
float fNoteFieldZoom = 1 - fTinyPercent*0.5f;
float fNoteFieldZoom = 1 - fMiniPercent*0.5f;
if( m_pNoteField )
m_pNoteField->SetZoom( fNoteFieldZoom );
if( m_pActorWithJudgmentPosition != NULL )
@@ -957,8 +1035,10 @@ void Player::Update( float fDeltaTime )
UpdateJudgedRows();
// Check for TapNote misses
UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() );
if (!GAMESTATE->m_bInStepEditor)
{
UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() );
}
// process transforms that are waiting to be applied
ApplyWaitingTransforms();
}
@@ -1323,14 +1403,9 @@ void Player::UpdateHoldNotes( int iSongRow, float fDeltaTime, vector<TrackRowTap
// Possibly fixed.
if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() )
{
if( tn.subType == TapNote::hold_head_roll )
{
m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", max(0.0, min(1.0, fLifeFraction * 2.0)));
}
else
{
m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", max(0.0, min(1.0, fLifeFraction * 10.0 - 8.5)));
}
float factor = (tn.subType == TapNote::hold_head_roll ? 2 : 10.0f - 8.5f);
factor *= fLifeFraction;
m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", max(0.0f, min(1.0f, factor)));
}
}
@@ -1375,7 +1450,7 @@ void Player::DrawPrimitives()
// May have both players in doubles (for battle play); only draw primary player.
if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_OnePlayerTwoSides &&
pn != GAMESTATE->m_MasterPlayerNumber )
pn != GAMESTATE->GetMasterPlayerNumber() )
return;
// Draw these below everything else.
@@ -1413,7 +1488,7 @@ void Player::DrawPrimitives()
float fTiltDegrees = SCALE(fTilt,-1.f,+1.f,+30,-30) * (bReverse?-1:1);
float fZoom = SCALE( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY], 0.f, 1.f, 1.f, 0.5f );
float fZoom = SCALE( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI], 0.f, 1.f, 1.f, 0.5f );
if( fTilt > 0 )
fZoom *= SCALE( fTilt, 0.f, 1.f, 1.f, 0.9f );
else
@@ -1437,7 +1512,7 @@ void Player::DrawPrimitives()
DISPLAY->PopMatrix();
// m_pNoteField->m_sprBoard->GetVisible()
if( !(bool)COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 )
if( !COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 )
if( m_sprCombo )
m_sprCombo->Draw();
@@ -1538,7 +1613,7 @@ int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool
// Is this the row we want?
do {
const TapNote &tn = begin->second;
if( m_Timing->IsWarpAtRow( begin->first ) || m_Timing->IsFakeAtRow( begin->first ) )
if (!m_Timing->IsJudgableAtRow( begin->first ))
break;
if( tn.type == TapNote::empty )
break;
@@ -1594,7 +1669,7 @@ int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool b
++iter;
continue;
}
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
if (!m_Timing->IsJudgableAtRow(iter.Row()))
{
++iter;
continue;
@@ -2118,7 +2193,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) &&
!m_Timing->IsWarpAtRow(iSongRow) && !m_Timing->IsFakeAtRow(iSongRow))
m_Timing->IsJudgableAtRow(iSongRow))
score = TNS_HitMine;
break;
@@ -2616,7 +2691,7 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
continue;
// Ignore all notes in WarpSegments or FakeSegments.
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
if (!m_Timing->IsJudgableAtRow(iter.Row()))
continue;
if( tn.type == TapNote::mine )
@@ -2651,7 +2726,7 @@ void Player::UpdateJudgedRows()
int iRow = iter.Row();
// Do not judge arrows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow(iRow) || m_Timing->IsFakeAtRow(iRow) )
if (!m_Timing->IsJudgableAtRow(iRow))
continue;
if( iLastSeenRow != iRow )
@@ -2852,7 +2927,8 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
// check to see if there's a note at the crossed row
if( m_pPlayerState->m_PlayerController != PC_HUMAN )
{
if( tn.type != TapNote::empty && tn.type != TapNote::fake && tn.result.tns == TNS_None )
if(tn.type != TapNote::empty && tn.type != TapNote::fake && tn.result.tns == TNS_None
&& this->m_Timing->IsJudgableAtRow(iRow) )
{
Step( iTrack, iRow, now, false, false );
if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY )
@@ -2866,8 +2942,10 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
}
// Update hold checkpoints
if( HOLD_CHECKPOINTS )
/* Update hold checkpoints
*
* TODO: Move this to a separate function. */
if( HOLD_CHECKPOINTS && m_pPlayerState->m_PlayerController != PC_AUTOPLAY )
{
int iCheckpointFrequencyRows = ROWS_PER_BEAT/2;
if( CHECKPOINTS_USE_TICKCOUNTS )
@@ -2878,19 +2956,21 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
}
else if( CHECKPOINTS_USE_TIME_SIGNATURES )
{
TimeSignatureSegment tSignature = m_Timing->GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
TimeSignatureSegment * tSignature = m_Timing->GetTimeSignatureSegmentAtRow( iLastRowCrossed );
// Most songs are in 4/4 time. The frequency for checking tick counts should reflect that.
iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature.GetDen() / (tSignature.GetNum() * 4);
iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature->GetDen() / (tSignature->GetNum() * 4);
}
if( iCheckpointFrequencyRows > 0 )
{
// "the first row after the start of the range that lands on a beat"
int iFirstCheckpointInRange = ((m_iFirstUncrossedRow+iCheckpointFrequencyRows-1)/iCheckpointFrequencyRows) * iCheckpointFrequencyRows;
int iFirstCheckpointInRange = ((m_iFirstUncrossedRow+iCheckpointFrequencyRows-1)
/iCheckpointFrequencyRows) * iCheckpointFrequencyRows;
// "the last row or first row earlier that lands on a beat"
int iLastCheckpointInRange = ((iLastRowCrossed)/iCheckpointFrequencyRows) * iCheckpointFrequencyRows;
int iLastCheckpointInRange = ((iLastRowCrossed)/iCheckpointFrequencyRows)
* iCheckpointFrequencyRows;
for( int r = iFirstCheckpointInRange; r <= iLastCheckpointInRange; r += iCheckpointFrequencyRows )
{
@@ -2912,17 +2992,22 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
int iTrack = nIter.Track();
// "the first row after the hold head that lands on a beat"
int iFirstCheckpointOfHold = ((iStartRow+iCheckpointFrequencyRows)/iCheckpointFrequencyRows) * iCheckpointFrequencyRows;
int iFirstCheckpointOfHold = ((iStartRow+iCheckpointFrequencyRows)/iCheckpointFrequencyRows)
* iCheckpointFrequencyRows;
// "the end row or the first earlier row that lands on a beat"
int iLastCheckpointOfHold = ((iEndRow)/iCheckpointFrequencyRows) * iCheckpointFrequencyRows;
int iLastCheckpointOfHold = ((iEndRow)/iCheckpointFrequencyRows)
* iCheckpointFrequencyRows;
// count the end of the hold as a checkpoint
bool bHoldOverlapsRow = iFirstCheckpointOfHold <= r && r <= iLastCheckpointOfHold;
if( !bHoldOverlapsRow )
continue;
viColsWithHold.push_back( iTrack );
if( tn.HoldResult.fLife > 0 )
{
++iNumHoldsHeldThisRow;
@@ -2934,11 +3019,15 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
++tn.HoldResult.iCheckpointsMissed;
}
}
GAMESTATE->SetProcessedTimingData(this->m_Timing);
// TODO: Find a better way of handling hold checkpoints with other taps.
if( !viColsWithHold.empty() && ( CHECKPOINTS_TAPS_SEPARATE_JUDGMENT || m_NoteData.GetNumTapNotesInRow( iLastRowCrossed ) == 0 ) )
{
HandleHoldCheckpoint( r, iNumHoldsHeldThisRow, iNumHoldsMissedThisRow, viColsWithHold );
HandleHoldCheckpoint(r,
iNumHoldsHeldThisRow,
iNumHoldsMissedThisRow,
viColsWithHold );
}
}
}
@@ -2965,10 +3054,6 @@ void Player::RandomizeNotes( int iNoteRow )
const int iSwapWith = RandomInt( iNumOfTracks );
// Make sure we're not swapping with ourselves.
if( t == iSwapWith )
continue;
// Make sure this is empty.
if( m_NoteData.FindTapNote(iSwapWith, iNewNoteRow) != m_NoteData.end(iSwapWith) )
continue;
@@ -2990,7 +3075,7 @@ void Player::HandleTapRowScore( unsigned row )
#endif
// Do not score rows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow( row ) || m_Timing->IsFakeAtRow( row ) )
if (!m_Timing->IsJudgableAtRow(row))
return;
if( GAMESTATE->m_bDemonstrationOrJukebox )
@@ -3086,7 +3171,10 @@ void Player::HandleTapRowScore( unsigned row )
ChangeLife( scoreOfLastTap );
}
void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumHoldsMissedThisRow, const vector<int> &viColsWithHold )
void Player::HandleHoldCheckpoint(int iRow,
int iNumHoldsHeldThisRow,
int iNumHoldsMissedThisRow,
const vector<int> &viColsWithHold )
{
bool bNoCheating = true;
#ifdef DEBUG
@@ -3094,7 +3182,7 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
#endif
// WarpSegments and FakeSegments aren't judged in any way.
if( m_Timing->IsWarpAtRow( iRow ) || m_Timing->IsFakeAtRow( iRow ) )
if (!m_Timing->IsJudgableAtRow(iRow))
return;
// don't accumulate combo if AutoPlay is on.
@@ -3105,9 +3193,15 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0;
if( m_pPrimaryScoreKeeper )
m_pPrimaryScoreKeeper->HandleHoldCheckpointScore( m_NoteData, iRow, iNumHoldsHeldThisRow, iNumHoldsMissedThisRow );
m_pPrimaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData,
iRow,
iNumHoldsHeldThisRow,
iNumHoldsMissedThisRow );
if( m_pSecondaryScoreKeeper )
m_pSecondaryScoreKeeper->HandleHoldCheckpointScore( m_NoteData, iRow, iNumHoldsHeldThisRow, iNumHoldsMissedThisRow );
m_pSecondaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData,
iRow,
iNumHoldsHeldThisRow,
iNumHoldsMissedThisRow );
if( iNumHoldsMissedThisRow == 0 )
{
@@ -3116,7 +3210,8 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
{
FOREACH_CONST( int, viColsWithHold, i )
{
bool bBright = m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD;
bool bBright = m_pPlayerStageStats
&& m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD;
if( m_pNoteField )
m_pNoteField->DidHoldNote( *i, HNS_Held, bBright );
}
@@ -3285,12 +3380,14 @@ void Player::SetCombo( int iCombo, int iMisses )
if( GAMESTATE->IsCourseMode() )
{
int iSongIndexStartColoring = GAMESTATE->m_pCurCourse->GetEstimatedNumStages();
iSongIndexStartColoring = static_cast<int>(floor(iSongIndexStartColoring*PERCENT_UNTIL_COLOR_COMBO));
iSongIndexStartColoring =
static_cast<int>(floor(iSongIndexStartColoring*PERCENT_UNTIL_COLOR_COMBO));
bPastBeginning = GAMESTATE->GetCourseSongIndex() >= iSongIndexStartColoring;
}
else
{
bPastBeginning = m_pPlayerState->m_Position.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 )
@@ -3332,13 +3429,29 @@ RString Player::ApplyRandomAttack()
class LunaPlayer: public Luna<Player>
{
public:
static int SetActorWithJudgmentPosition( T* p, lua_State *L ) { Actor *pActor = Luna<Actor>::check(L, 1); p->SetActorWithJudgmentPosition(pActor); return 0; }
static int SetActorWithComboPosition( T* p, lua_State *L ) { Actor *pActor = Luna<Actor>::check(L, 1); p->SetActorWithComboPosition(pActor); return 0; }
static int SetActorWithJudgmentPosition( T* p, lua_State *L )
{
Actor *pActor = Luna<Actor>::check(L, 1);
p->SetActorWithJudgmentPosition(pActor);
return 0;
}
static int SetActorWithComboPosition( T* p, lua_State *L )
{
Actor *pActor = Luna<Actor>::check(L, 1);
p->SetActorWithComboPosition(pActor);
return 0;
}
static int GetPlayerTimingData( T* p, lua_State *L )
{
p->GetPlayerTimingData().PushSelf(L);
return 1;
}
LunaPlayer()
{
ADD_METHOD( SetActorWithJudgmentPosition );
ADD_METHOD( SetActorWithComboPosition );
ADD_METHOD( GetPlayerTimingData );
}
};
+10
View File
@@ -69,6 +69,16 @@ public:
void Load();
void CrossedRows( int iLastRowCrossed, const RageTimer &now );
bool IsOniDead() const;
/**
* @brief Retrieve the Player's TimingData.
*
* This is primarily for a lua hook.
* @return the TimingData in question. */
TimingData GetPlayerTimingData() const
{
return *(this->m_Timing);
}
// Called when a fret, step, or strum type button changes
void Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease );
+71 -19
View File
@@ -12,6 +12,9 @@
#include "CommonMetrics.h"
#include <float.h>
void NextFloat( float fValues[], int size );
void NextBool( bool bValues[], int size );
ThemeMetric<float> RANDOM_SPEED_CHANCE ( "PlayerOptions", "RandomSpeedChance" );
ThemeMetric<float> RANDOM_REVERSE_CHANCE ( "PlayerOptions", "RandomReverseChance" );
ThemeMetric<float> RANDOM_DARK_CHANCE ( "PlayerOptions", "RandomDarkChance" );
@@ -23,6 +26,7 @@ ThemeMetric<float> RANDOM_SUDDEN_CHANCE ( "PlayerOptions", "RandomSuddenChance"
void PlayerOptions::Init()
{
m_bSetScrollSpeed = false;
m_fMaxScrollBPM = 0; m_SpeedfMaxScrollBPM = 1.0f;
m_fTimeSpacing = 0; m_SpeedfTimeSpacing = 1.0f;
m_fScrollSpeed = 1.0f; m_SpeedfScrollSpeed = 1.0f;
m_fScrollBPM = 200; m_SpeedfScrollBPM = 1.0f;
@@ -34,7 +38,7 @@ void PlayerOptions::Init()
m_fBlind = 0; m_SpeedfBlind = 1.0f;
m_fCover = 0; m_SpeedfCover = 1.0f;
m_fRandAttack = 0; m_SpeedfRandAttack = 1.0f;
m_fSongAttack = 0; m_SpeedfSongAttack = 1.0f;
m_fNoAttack = 0; m_SpeedfNoAttack = 1.0f;
m_fPlayerAutoPlay = 0; m_SpeedfPlayerAutoPlay = 1.0f;
m_bSetTiltOrSkew = false;
m_fPerspectiveTilt = 0; m_SpeedfPerspectiveTilt = 1.0f;
@@ -45,7 +49,6 @@ void PlayerOptions::Init()
ZERO( m_bTransforms );
m_bMuteOnError = false;
m_FailType = FAIL_IMMEDIATE;
m_ScoreDisplay = SCORING_ADD;
m_sNoteSkin = "";
}
@@ -58,6 +61,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
APPROACH( fTimeSpacing );
APPROACH( fScrollSpeed );
//APPROACH( fMaxScrollBPM );
fapproach( m_fScrollBPM, other.m_fScrollBPM, fDeltaSeconds * other.m_SpeedfScrollBPM*150 );
for( int i=0; i<NUM_ACCELS; i++ )
APPROACH( fAccels[i] );
@@ -71,7 +75,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
APPROACH( fBlind );
APPROACH( fCover );
APPROACH( fRandAttack );
APPROACH( fSongAttack );
APPROACH( fNoAttack );
APPROACH( fPlayerAutoPlay );
APPROACH( fPerspectiveTilt );
APPROACH( fSkew );
@@ -85,7 +89,6 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
for( int i=0; i<NUM_TRANSFORMS; i++ )
DO_COPY( m_bTransforms[i] );
DO_COPY( m_bMuteOnError );
DO_COPY( m_ScoreDisplay );
DO_COPY( m_FailType );
DO_COPY( m_sNoteSkin );
#undef APPROACH
@@ -115,6 +118,11 @@ void PlayerOptions::GetMods( vector<RString> &AddTo, bool bForceNoteSkin ) const
if( !m_fTimeSpacing )
{
if( m_fMaxScrollBPM )
{
RString s = ssprintf( "m%.0f", m_fMaxScrollBPM );
AddTo.push_back( s );
}
if( m_bSetScrollSpeed || m_fScrollSpeed != 1 )
{
/* -> 1.00 */
@@ -179,7 +187,7 @@ void PlayerOptions::GetMods( vector<RString> &AddTo, bool bForceNoteSkin ) const
AddPart( AddTo, m_fCover, "Cover" );
AddPart( AddTo, m_fRandAttack, "RandomAttacks" );
AddPart( AddTo, m_fSongAttack, "SongAttacks" );
AddPart( AddTo, m_fNoAttack, "NoAttacks" );
AddPart( AddTo, m_fPlayerAutoPlay, "PlayerAutoPlay" );
AddPart( AddTo, m_fPassmark, "Passmark" );
@@ -331,6 +339,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
SET_FLOAT( fScrollSpeed )
SET_FLOAT( fTimeSpacing )
m_fTimeSpacing = 0;
m_fMaxScrollBPM = 0;
}
else if( sscanf( sBit, "c%f", &level ) == 1 )
{
@@ -339,17 +348,18 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
SET_FLOAT( fScrollBPM )
SET_FLOAT( fTimeSpacing )
m_fTimeSpacing = 1;
m_fMaxScrollBPM = 0;
}
/* Port M-Mods from OpenITG, starting from r537 */
// Midiman
// oITG's m-mods
// XXX: will not properly tween, I don't think.
else if( sscanf( sBit, "m%f", &level ) == 1 )
{
if( !isfinite(level) || level <= 0.0f )
level = 200.0f; // Just pick some value.
SET_FLOAT( fScrollBPM )
SET_FLOAT( fTimeSpacing )
m_fTimeSpacing = 1;
level = 200.0f;
SET_FLOAT( fMaxScrollBPM )
m_fTimeSpacing = 0;
}
else if( sBit == "clearall" ) Init();
else if( sBit == "boost" ) SET_FLOAT( fAccels[ACCEL_BOOST] )
else if( sBit == "brake" || sBit == "land" ) SET_FLOAT( fAccels[ACCEL_BRAKE] )
@@ -416,7 +426,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
else if( sBit == "blind" ) SET_FLOAT( fBlind )
else if( sBit == "cover" ) SET_FLOAT( fCover )
else if( sBit == "randomattacks" ) SET_FLOAT( fRandAttack )
else if( sBit == "songattacks" ) SET_FLOAT( fSongAttack )
else if( sBit == "noattacks" ) SET_FLOAT( fNoAttack )
else if( sBit == "playerautoplay" ) SET_FLOAT( fPlayerAutoPlay )
else if( sBit == "passmark" ) SET_FLOAT( fPassmark )
else if( sBit == "overhead" ) { m_bSetTiltOrSkew = true; m_fSkew = 0; m_fPerspectiveTilt = 0; m_SpeedfSkew = m_SpeedfPerspectiveTilt = speed; }
@@ -439,9 +449,6 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
GAMESTATE->GetDefaultPlayerOptions( po );
m_FailType = po.m_FailType;
}
else if( sBit == "addscore" ) m_ScoreDisplay = SCORING_ADD;
else if( sBit == "subtractscore" ) m_ScoreDisplay = SCORING_SUBTRACT;
else if( sBit == "averagescore" ) m_ScoreDisplay = SCORING_AVERAGE;
else if( sBit == "muteonerror" ) m_bMuteOnError = on;
else if( sBit == "random" ) ChooseRandomModifiers();
// deprecated mods/left in for compatibility
@@ -652,15 +659,15 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
COMPARE(m_fTimeSpacing);
COMPARE(m_fScrollSpeed);
COMPARE(m_fScrollBPM);
COMPARE(m_fMaxScrollBPM);
COMPARE(m_fRandomSpeed);
COMPARE(m_ScoreDisplay);
COMPARE(m_FailType);
COMPARE(m_bMuteOnError);
COMPARE(m_fDark);
COMPARE(m_fBlind);
COMPARE(m_fCover);
COMPARE(m_fRandAttack);
COMPARE(m_fSongAttack);
COMPARE(m_fNoAttack);
COMPARE(m_fPlayerAutoPlay);
COMPARE(m_fPerspectiveTilt);
COMPARE(m_fSkew);
@@ -715,7 +722,36 @@ bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerN
// This makes songs with sparse notes easier.
if( m_bTransforms[TRANSFORM_ECHO] ) return true;
// Removing attacks is easier in general.
if (m_fNoAttack || (!m_fRandAttack && pSteps->HasAttacks()))
return true;
if( m_fCover ) return true;
// M-mods make songs with indefinite BPMs easier because
// they ensure that the song has a scrollable speed.
if( m_fMaxScrollBPM != 0 )
{
// BPM display is obfuscated
// if( pSong->m_DisplayBPMType == DISPLAY_BPM_RANDOM )
// return true;
DisplayBpms bpms;
if( GAMESTATE->IsCourseMode() )
{
Trail *pTrail = GAMESTATE->m_pCurCourse->GetTrail( GAMESTATE->GetCurrentStyle()->m_StepsType );
pTrail->GetDisplayBpms( bpms );
}
else
{
GAMESTATE->m_pCurSong->GetDisplayBpms( bpms );
}
pSong->GetDisplayBpms( bpms );
// maximum BPM is obfuscated, so M-mods will set a playable speed.
if( bpms.GetMax() <= 0 )
return true;
}
if( m_fPlayerAutoPlay ) return true;
return false;
}
@@ -787,6 +823,7 @@ RString PlayerOptions::GetSavedPrefsString() const
SAVE( m_fTimeSpacing );
SAVE( m_fScrollSpeed );
SAVE( m_fScrollBPM );
SAVE( m_fMaxScrollBPM );
SAVE( m_fScrolls[SCROLL_REVERSE] );
SAVE( m_fPerspectiveTilt );
SAVE( m_bTransforms[TRANSFORM_NOHOLDS] );
@@ -798,7 +835,6 @@ RString PlayerOptions::GetSavedPrefsString() const
SAVE( m_bTransforms[TRANSFORM_NOSTRETCH] );
SAVE( m_bTransforms[TRANSFORM_NOLIFTS] );
SAVE( m_bTransforms[TRANSFORM_NOFAKES] );
SAVE( m_ScoreDisplay );
SAVE( m_bMuteOnError );
SAVE( m_sNoteSkin );
#undef SAVE
@@ -816,6 +852,7 @@ void PlayerOptions::ResetPrefs( ResetPrefsType type )
CPY( m_fTimeSpacing );
CPY( m_fScrollSpeed );
CPY( m_fScrollBPM );
CPY( m_fMaxScrollBPM );
break;
case saved_prefs_invalid_for_course:
break;
@@ -961,7 +998,20 @@ public:
DEFINE_METHOD( GetBlind, m_fBlind )
DEFINE_METHOD( GetCover, m_fCover )
DEFINE_METHOD( GetRandomAttacks, m_fRandAttack )
DEFINE_METHOD( GetSongAttacks, m_fSongAttack )
static int GetStepAttacks( T *p, lua_State *L )
{
lua_pushnumber(L,
(p->m_fNoAttack > 0 || p->m_fRandAttack > 0 ? 0 : 1 ));
return 1;
}
// This one is deprecated.
static int GetSongAttacks( T *p, lua_State *L )
{
return GetStepAttacks(p, L);
}
DEFINE_METHOD( GetNoAttacks, m_fNoAttack )
DEFINE_METHOD( GetSkew, m_fSkew )
DEFINE_METHOD( GetPassmark, m_fPassmark )
DEFINE_METHOD( GetRandomSpeed, m_fRandomSpeed )
@@ -988,6 +1038,8 @@ public:
// SetSkew
ADD_METHOD( GetSongAttacks );
// SetSongAttacks
ADD_METHOD( GetStepAttacks );
ADD_METHOD( GetNoAttacks );
ADD_METHOD( GetCMod );
ADD_METHOD( GetXMod );
+4 -11
View File
@@ -27,15 +27,14 @@ public:
m_fBlind(0), m_SpeedfBlind(1.0f),
m_fCover(0), m_SpeedfCover(1.0f),
m_fRandAttack(0), m_SpeedfRandAttack(1.0f),
m_fSongAttack(0), m_SpeedfSongAttack(1.0f),
m_fNoAttack(0), m_SpeedfNoAttack(1.0f),
m_fPlayerAutoPlay(0), m_SpeedfPlayerAutoPlay(1.0f),
m_bSetTiltOrSkew(false),
m_fPerspectiveTilt(0), m_SpeedfPerspectiveTilt(1.0f),
m_fSkew(0), m_SpeedfSkew(1.0f),
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_bMuteOnError(false), m_FailType(FAIL_IMMEDIATE)
{
m_sNoteSkin = "";
ZERO( m_fAccels ); ONE( m_SpeedfAccels );
@@ -151,12 +150,6 @@ public:
SCROLL_CENTERED,
NUM_SCROLLS
};
enum ScoreDisplay {
SCORING_ADD=0,
SCORING_SUBTRACT,
SCORING_AVERAGE,
NUM_SCOREDISPLAYS
};
float GetReversePercentForColumn( int iCol ) const; // accounts for all Directions
@@ -164,6 +157,7 @@ public:
* PlayerOptions::Approach approaches. */
bool m_bSetScrollSpeed; // true if the scroll speed was set by FromString
float m_fTimeSpacing, m_SpeedfTimeSpacing; // instead of Beat spacing (CMods, mMods)
float m_fMaxScrollBPM, m_SpeedfMaxScrollBPM;
float m_fScrollSpeed, m_SpeedfScrollSpeed; // used if !m_bTimeSpacing (xMods)
float m_fScrollBPM, m_SpeedfScrollBPM; // used if m_bTimeSpacing (CMod)
float m_fAccels[NUM_ACCELS], m_SpeedfAccels[NUM_ACCELS];
@@ -174,7 +168,7 @@ public:
float m_fBlind, m_SpeedfBlind;
float m_fCover, m_SpeedfCover; // hide the background per-player--can't think of a good name
float m_fRandAttack, m_SpeedfRandAttack;
float m_fSongAttack, m_SpeedfSongAttack;
float m_fNoAttack, m_SpeedfNoAttack;
float m_fPlayerAutoPlay, m_SpeedfPlayerAutoPlay;
bool m_bSetTiltOrSkew; // true if the tilt or skew was set by FromString
float m_fPerspectiveTilt, m_SpeedfPerspectiveTilt; // -1 = near, 0 = overhead, +1 = space
@@ -198,7 +192,6 @@ public:
};
/** @brief The method for which a player can fail a song. */
FailType m_FailType;
ScoreDisplay m_ScoreDisplay;
/**
* @brief The Noteskin to use.
+1 -1
View File
@@ -162,7 +162,7 @@ void PlayerState::RebuildPlayerOptionsFromActiveAttacks()
so.FromString( m_ActiveAttacks[s].sModifiers );
}
m_PlayerOptions.Assign( ModsLevel_Song, po );
if( m_PlayerNumber == GAMESTATE->m_MasterPlayerNumber )
if( m_PlayerNumber == GAMESTATE->GetMasterPlayerNumber() )
GAMESTATE->m_SongOptions.Assign( ModsLevel_Song, so );
int iSumOfAttackLevels = GetSumOfActiveAttackLevels();
+3
View File
@@ -5,6 +5,9 @@
class IniFile;
void ValidateDisplayAspectRatio( float &val );
void ValidateSongsPerPlay( int &val );
/** @brief How many songs can be played during a normal game max?
*
* This assumes no extra stages, no event mode, no course modes. */
+2 -2
View File
@@ -35,7 +35,7 @@
* </li></ul>
*/
#ifndef PRODUCT_VER_BARE
#define PRODUCT_VER_BARE v5.0 Preview 1a
#define PRODUCT_VER_BARE v5.0 Preview 2
#endif
/**
@@ -54,7 +54,7 @@
#define PRODUCT_VER PRODUCT_XSTRINGIFY(PRODUCT_VER_BARE)
#define PRODUCT_ID_VER PRODUCT_XSTRINGIFY(PRODUCT_ID_VER_BARE)
#define VIDEO_TROUBLESHOOTING_URL "http://www.stepmania.com/stepmania/mediawiki.php?title=Video_Driver_Troubleshooting"
#define VIDEO_TROUBLESHOOTING_URL "http://www.stepmania.com/stepmaniawiki.php?title=Video_Driver_Troubleshooting"
/** @brief The URL to report bugs on the program. */
#define REPORT_BUG_URL "http://ssc.ajworld.net/sm-ssc/bugtracker/"
+3 -3
View File
@@ -5,12 +5,12 @@
; see ProductInfo.h for use descriptions
!define PRODUCT_ID "StepMania"
!define PRODUCT_VER "v5.0 Preview 1a"
!define PRODUCT_VER "v5.0 Preview 2"
!define PRODUCT_DISPLAY "${PRODUCT_ID} ${PRODUCT_VER}"
!define PRODUCT_BITMAP "ssc"
!define PRODUCT_BITMAP "sm5"
!define PRODUCT_URL "http://www.stepmania.com/"
!define UPDATES_URL "http://www.stepmania.com/"
!define UPDATES_URL "http://code.google.com/p/sm-ssc/downloads/list"
;!define INSTALL_EXTERNAL_PCKS
;!define INSTALL_INTERNAL_PCKS
+1 -1
View File
@@ -112,7 +112,7 @@ public:
bpp(0), rate(0), vsync(false), interlaced(false),
bSmoothLines(false), bTrilinearFiltering(false),
bAnisotropicFiltering(false), sWindowTitle(RString()),
sIconFile(RString()), PAL(false), fDisplayAspectRatio(0.0) {}
sIconFile(RString()), PAL(false), fDisplayAspectRatio(0.0f) {}
bool windowed;
int width;
+11 -6
View File
@@ -31,6 +31,13 @@ using namespace RageDisplay_Legacy_Helpers;
#define glFlush()
#endif
RString GetInfoLog( GLhandleARB h );
GLhandleARB CompileShader( GLenum ShaderType, RString sFile, vector<RString> asDefines );
GLhandleARB LoadShader( GLenum ShaderType, RString sFile, vector<RString> asDefines );
void InitShaders();
void SetupExtensions();
void SetPixelMapForSurface( int glImageFormat, int glTexFormat, const RageSurfacePalette *palette );
//
// Globals
//
@@ -333,7 +340,7 @@ GLhandleARB CompileShader( GLenum ShaderType, RString sFile, vector<RString> asD
GLhandleARB LoadShader( GLenum ShaderType, RString sFile, vector<RString> asDefines )
{
// Don't do anything here if not the hardware/driver can't do it!
if (!GLEW_ARB_fragment_program && GLEW_ARB_shading_language_100 && ShaderType == GL_FRAGMENT_SHADER_ARB)
if (!GLEW_ARB_fragment_shader && ShaderType == GL_FRAGMENT_SHADER_ARB)
return 0;
if (!GLEW_ARB_vertex_shader && ShaderType == GL_VERTEX_SHADER_ARB)
return 0;
@@ -1655,7 +1662,7 @@ void RageDisplay_Legacy::SetTextureFiltering( TextureUnit tu, bool b )
void RageDisplay_Legacy::SetEffectMode( EffectMode effect )
{
if (!GLEW_ARB_fragment_program && !GLEW_ARB_shading_language_100)
if (!GLEW_ARB_fragment_shader)
return;
GLhandleARB hShader = 0;
@@ -2561,7 +2568,7 @@ RString RageDisplay_Legacy::GetTextureDiagnostics(unsigned iTexture) const
void RageDisplay_Legacy::SetAlphaTest(bool b)
{
// Previously this was 0.01, rather than 0x01.
glAlphaFunc(GL_GREATER, 0.00390625 /* 1/256 */);
glAlphaFunc(GL_GREATER, 0.00390625f /* 1/256 */);
if (b)
glEnable(GL_ALPHA_TEST);
else
@@ -2641,11 +2648,9 @@ void RageDisplay_Legacy::SetSphereEnvironmentMapping(TextureUnit tu, bool b)
}
}
GLint iCelTexture1, iCelTexture2 = 0;
void RageDisplay_Legacy::SetCelShaded( int stage )
{
if (!GLEW_ARB_fragment_program && !GL_ARB_shading_language_100)
if (!GLEW_ARB_fragment_shader)
return; // not supported
switch (stage)

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