fb1d69add3
Don't require IsPersistentProfile in some places related to fitness mode where a non-persistent Profile will suffice
1107 lines
29 KiB
C++
1107 lines
29 KiB
C++
#include "global.h"
|
|
#include "GameCommand.h"
|
|
#include "RageUtil.h"
|
|
#include "RageLog.h"
|
|
#include "GameManager.h"
|
|
#include "GameState.h"
|
|
#include "RageDisplay.h"
|
|
#include "AnnouncerManager.h"
|
|
#include "ProfileManager.h"
|
|
#include "StepMania.h"
|
|
#include "ScreenManager.h"
|
|
#include "SongManager.h"
|
|
#include "PrefsManager.h"
|
|
#include "arch/ArchHooks/ArchHooks.h"
|
|
#include "MemoryCardManager.h"
|
|
#include "song.h"
|
|
#include "Game.h"
|
|
#include "Style.h"
|
|
#include "Foreach.h"
|
|
#include "Command.h"
|
|
#include "arch/Dialog/Dialog.h"
|
|
#include "Bookkeeper.h"
|
|
#include "UnlockManager.h"
|
|
#include "GameSoundManager.h"
|
|
#include "ThemeManager.h"
|
|
#include "PlayerState.h"
|
|
#include "Course.h"
|
|
#include "RageFileManager.h"
|
|
|
|
void GameCommand::Init()
|
|
{
|
|
m_sName = "";
|
|
m_bInvalid = true;
|
|
m_iIndex = -1;
|
|
m_pGame = NULL;
|
|
m_pStyle = NULL;
|
|
m_pm = PLAY_MODE_INVALID;
|
|
m_dc = DIFFICULTY_INVALID;
|
|
m_CourseDifficulty = DIFFICULTY_INVALID;
|
|
m_sModifiers = "";
|
|
m_sAnnouncer = "";
|
|
m_sScreen = "";
|
|
m_LuaFunction.Unset();
|
|
m_pSong = NULL;
|
|
m_pSteps = NULL;
|
|
m_pCourse = NULL;
|
|
m_pTrail = NULL;
|
|
m_pCharacter = NULL;
|
|
m_SortOrder = SORT_INVALID;
|
|
m_iUnlockIndex = -1;
|
|
m_sSoundPath = "";
|
|
m_vsScreensToPrepare.clear();
|
|
m_bDeletePreparedScreens = false;
|
|
m_iWeightPounds = -1;
|
|
m_iGoalCalories = -1;
|
|
m_GoalType = GOAL_INVALID;
|
|
|
|
m_bClearBookkeepingData = false;
|
|
m_bClearMachineStats = false;
|
|
m_bClearMachineEdits = false;
|
|
m_bFillMachineStats = false;
|
|
m_bTransferStatsFromMachine = false;
|
|
m_bTransferStatsToMachine = false;
|
|
m_bCopyEditsFromMachine = false;
|
|
m_bCopyEditsToMachine = false;
|
|
m_bInsertCredit = false;
|
|
m_bResetToFactoryDefaults = false;
|
|
m_bStopMusic = false;
|
|
m_bApplyDefaultOptions = false;
|
|
}
|
|
|
|
bool CompareSongOptions( const SongOptions &so1, const SongOptions &so2 );
|
|
|
|
bool GameCommand::DescribesCurrentModeForAllPlayers() const
|
|
{
|
|
FOREACH_HumanPlayer( pn )
|
|
if( !DescribesCurrentMode(pn) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GameCommand::DescribesCurrentMode( PlayerNumber pn ) const
|
|
{
|
|
if( m_pGame != NULL && m_pGame != GAMESTATE->m_pCurGame )
|
|
return false;
|
|
if( m_pm != PLAY_MODE_INVALID && GAMESTATE->m_PlayMode != m_pm )
|
|
return false;
|
|
if( m_pStyle && GAMESTATE->m_pCurStyle.Get() != m_pStyle )
|
|
return false;
|
|
// HACK: don't compare m_dc if m_pSteps is set. This causes problems
|
|
// in ScreenSelectOptionsMaster::ImportOptions if m_PreferredDifficulty
|
|
// doesn't match the difficulty of m_pCurSteps.
|
|
if( m_pSteps == NULL && m_dc != DIFFICULTY_INVALID )
|
|
{
|
|
// Why is this checking for all players?
|
|
FOREACH_HumanPlayer( pn )
|
|
if( GAMESTATE->m_PreferredDifficulty[pn] != m_dc )
|
|
return false;
|
|
}
|
|
|
|
if( m_sAnnouncer != "" && m_sAnnouncer != ANNOUNCER->GetCurAnnouncerName() )
|
|
return false;
|
|
|
|
if( m_sModifiers != "" )
|
|
{
|
|
/* Apply modifiers. */
|
|
PlayerOptions po = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions;
|
|
SongOptions so = GAMESTATE->m_SongOptions;
|
|
po.FromString( m_sModifiers );
|
|
so.FromString( m_sModifiers );
|
|
|
|
if( po != GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions )
|
|
return false;
|
|
if( so != GAMESTATE->m_SongOptions )
|
|
return false;
|
|
}
|
|
|
|
if( m_pSong && GAMESTATE->m_pCurSong.Get() != m_pSong )
|
|
return false;
|
|
if( m_pSteps && GAMESTATE->m_pCurSteps[pn].Get() != m_pSteps )
|
|
return false;
|
|
if( m_pCharacter && GAMESTATE->m_pCurCharacters[pn] != m_pCharacter )
|
|
return false;
|
|
if( m_pCourse && GAMESTATE->m_pCurCourse != m_pCourse )
|
|
return false;
|
|
if( m_pTrail && GAMESTATE->m_pCurTrail[pn] != m_pTrail )
|
|
return false;
|
|
if( !m_sSongGroup.empty() && GAMESTATE->m_sPreferredSongGroup != m_sSongGroup )
|
|
return false;
|
|
if( m_SortOrder != SORT_INVALID && GAMESTATE->m_PreferredSortOrder != m_SortOrder )
|
|
return false;
|
|
if( m_iWeightPounds != -1 && PROFILEMAN->GetProfile(pn)->m_iWeightPounds != m_iWeightPounds )
|
|
return false;
|
|
if( m_iGoalCalories != -1 && PROFILEMAN->GetProfile(pn)->m_iGoalCalories != m_iGoalCalories )
|
|
return false;
|
|
if( m_GoalType != GOAL_INVALID && PROFILEMAN->GetProfile(pn)->m_GoalType != m_GoalType )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameCommand::Load( int iIndex, const Commands& cmds )
|
|
{
|
|
m_iIndex = iIndex;
|
|
m_bInvalid = false;
|
|
m_Commands = cmds;
|
|
|
|
FOREACH_CONST( Command, cmds.v, cmd )
|
|
LoadOne( *cmd );
|
|
}
|
|
|
|
void GameCommand::LoadOne( const Command& cmd )
|
|
{
|
|
CString sName = cmd.GetName();
|
|
if( sName.empty() )
|
|
return;
|
|
|
|
CString sValue;
|
|
for( unsigned i = 1; i < cmd.m_vsArgs.size(); ++i )
|
|
{
|
|
if( i > 1 )
|
|
sValue += ",";
|
|
sValue += (CString) cmd.GetArg(i);
|
|
}
|
|
|
|
if( sName == "game" )
|
|
{
|
|
const Game* pGame = GAMEMAN->StringToGameType( sValue );
|
|
if( pGame != NULL )
|
|
m_pGame = pGame;
|
|
else
|
|
m_bInvalid |= true;
|
|
}
|
|
|
|
else if( sName == "style" )
|
|
{
|
|
const Style* style = GAMEMAN->GameAndStringToStyle( GAMESTATE->m_pCurGame, sValue );
|
|
if( style )
|
|
m_pStyle = style;
|
|
else
|
|
m_bInvalid |= true;
|
|
}
|
|
|
|
else if( sName == "playmode" )
|
|
{
|
|
PlayMode pm = StringToPlayMode( sValue );
|
|
if( pm != PLAY_MODE_INVALID )
|
|
m_pm = pm;
|
|
else
|
|
m_bInvalid |= true;
|
|
}
|
|
|
|
else if( sName == "difficulty" )
|
|
{
|
|
Difficulty dc = StringToDifficulty( sValue );
|
|
if( dc != DIFFICULTY_INVALID )
|
|
m_dc = dc;
|
|
else
|
|
m_bInvalid |= true;
|
|
}
|
|
|
|
else if( sName == "announcer" )
|
|
{
|
|
m_sAnnouncer = sValue;
|
|
}
|
|
|
|
else if( sName == "name" )
|
|
{
|
|
m_sName = sValue;
|
|
}
|
|
|
|
else if( sName == "mod" )
|
|
{
|
|
if( m_sModifiers != "" )
|
|
m_sModifiers += ",";
|
|
m_sModifiers += sValue;
|
|
}
|
|
|
|
else if( sName == "lua" )
|
|
{
|
|
m_LuaFunction.SetFromExpression( sValue );
|
|
ASSERT_M( !m_LuaFunction.IsNil(), ssprintf("\"%s\" evaluated to nil", sValue.c_str()) );
|
|
}
|
|
|
|
else if( sName == "screen" )
|
|
{
|
|
m_sScreen = sValue;
|
|
}
|
|
|
|
else if( sName == "song" )
|
|
{
|
|
m_pSong = SONGMAN->FindSong( sValue );
|
|
if( m_pSong == NULL )
|
|
{
|
|
m_sInvalidReason = ssprintf( "Song \"%s\" not found", sValue.c_str() );
|
|
m_bInvalid |= true;
|
|
}
|
|
}
|
|
|
|
else if( sName == "steps" )
|
|
{
|
|
CString sSteps = sValue;
|
|
|
|
/* This must be processed after "song" and "style" commands. */
|
|
if( !m_bInvalid )
|
|
{
|
|
Song *pSong = (m_pSong != NULL)? m_pSong:GAMESTATE->m_pCurSong;
|
|
const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->m_pCurStyle;
|
|
if( pSong == NULL || pStyle == NULL )
|
|
RageException::Throw( "Must set Song and Style to set Steps" );
|
|
|
|
Difficulty dc = StringToDifficulty( sSteps );
|
|
if( dc != DIFFICULTY_EDIT )
|
|
m_pSteps = pSong->GetStepsByDifficulty( pStyle->m_StepsType, dc );
|
|
else
|
|
m_pSteps = pSong->GetStepsByDescription( pStyle->m_StepsType, sSteps );
|
|
if( m_pSteps == NULL )
|
|
{
|
|
m_sInvalidReason = "steps not found";
|
|
m_bInvalid |= true;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if( sName == "course" )
|
|
{
|
|
m_pCourse = SONGMAN->FindCourse( sValue );
|
|
if( m_pCourse == NULL )
|
|
{
|
|
m_sInvalidReason = ssprintf( "Course \"%s\" not found", sValue.c_str() );
|
|
m_bInvalid |= true;
|
|
}
|
|
}
|
|
|
|
else if( sName == "trail" )
|
|
{
|
|
CString sTrail = sValue;
|
|
|
|
/* This must be processed after "course" and "style" commands. */
|
|
if( !m_bInvalid )
|
|
{
|
|
Course *pCourse = (m_pCourse != NULL)? m_pCourse:GAMESTATE->m_pCurCourse;
|
|
const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->m_pCurStyle;
|
|
if( pCourse == NULL || pStyle == NULL )
|
|
RageException::Throw( "Must set Course and Style to set Steps" );
|
|
|
|
const CourseDifficulty cd = StringToCourseDifficulty( sTrail );
|
|
ASSERT_M( cd != DIFFICULTY_INVALID, ssprintf("Invalid difficulty '%s'", sTrail.c_str()) );
|
|
|
|
m_pTrail = pCourse->GetTrail( pStyle->m_StepsType, cd );
|
|
if( m_pTrail == NULL )
|
|
{
|
|
m_sInvalidReason = "trail not found";
|
|
m_bInvalid |= true;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if( sName == "setenv" )
|
|
{
|
|
if( cmd.m_vsArgs.size() == 3 )
|
|
m_SetEnv[ cmd.m_vsArgs[1] ] = cmd.m_vsArgs[2];
|
|
}
|
|
|
|
else if( sName == "songgroup" )
|
|
{
|
|
m_sSongGroup = sValue;
|
|
}
|
|
|
|
else if( sName == "sort" )
|
|
{
|
|
m_SortOrder = StringToSortOrder( sValue );
|
|
if( m_SortOrder == SORT_INVALID )
|
|
{
|
|
m_sInvalidReason = ssprintf( "SortOrder \"%s\" is not valid.", sValue.c_str() );
|
|
m_bInvalid |= true;
|
|
}
|
|
}
|
|
|
|
else if( sName == "weight" )
|
|
{
|
|
m_iWeightPounds = atoi( sValue );
|
|
}
|
|
|
|
else if( sName == "goalcalories" )
|
|
{
|
|
m_iGoalCalories = atoi( sValue );
|
|
}
|
|
|
|
else if( sName == "goaltype" )
|
|
{
|
|
m_GoalType = StringToGoalType( sValue );
|
|
}
|
|
|
|
else if( sName == "unlock" )
|
|
{
|
|
m_iUnlockIndex = atoi( sValue );
|
|
}
|
|
|
|
else if( sName == "sound" )
|
|
{
|
|
m_sSoundPath = sValue;
|
|
}
|
|
|
|
else if( sName == "preparescreen" )
|
|
{
|
|
m_vsScreensToPrepare.push_back( sValue );
|
|
}
|
|
|
|
else if( sName == "deletepreparedscreens" )
|
|
{
|
|
m_bDeletePreparedScreens = true;
|
|
}
|
|
|
|
else if( sName == "clearbookkeepingdata" )
|
|
{
|
|
m_bClearBookkeepingData = true;
|
|
}
|
|
else if( sName == "clearmachinestats" )
|
|
{
|
|
m_bClearMachineStats = true;
|
|
}
|
|
else if( sName == "clearmachineedits" )
|
|
{
|
|
m_bClearMachineEdits = true;
|
|
}
|
|
else if( sName == "fillmachinestats" )
|
|
{
|
|
m_bFillMachineStats = true;
|
|
}
|
|
else if( sName == "transferstatsfrommachine" )
|
|
{
|
|
m_bTransferStatsFromMachine = true;
|
|
}
|
|
else if( sName == "copyeditstomachine" )
|
|
{
|
|
m_bCopyEditsToMachine = true;
|
|
}
|
|
else if( sName == "copyeditsfrommachine" )
|
|
{
|
|
m_bCopyEditsFromMachine = true;
|
|
}
|
|
else if( sName == "transferstatstomachine" )
|
|
{
|
|
m_bTransferStatsToMachine = true;
|
|
}
|
|
else if( sName == "insertcredit" )
|
|
{
|
|
m_bInsertCredit = true;
|
|
}
|
|
else if( sName == "resettofactorydefaults" )
|
|
{
|
|
m_bResetToFactoryDefaults = true;
|
|
}
|
|
else if( sName == "stopmusic" )
|
|
{
|
|
m_bStopMusic = true;
|
|
}
|
|
else if( sName == "applydefaultoptions" )
|
|
{
|
|
m_bApplyDefaultOptions = true;
|
|
}
|
|
|
|
else
|
|
{
|
|
CString sWarning = ssprintf( "Command '%s' is not valid.", cmd.GetOriginalCommandString().c_str() );
|
|
LOG->Warn( sWarning );
|
|
Dialog::OK( sWarning, "INVALID_GAME_COMMAND" );
|
|
}
|
|
}
|
|
|
|
int GetNumCreditsPaid()
|
|
{
|
|
int iNumCreditsPaid = GAMESTATE->GetNumSidesJoined();
|
|
|
|
// players other than the first joined for free
|
|
if( GAMESTATE->GetPremium() == PREMIUM_JOINT )
|
|
iNumCreditsPaid = min( iNumCreditsPaid, 1 );
|
|
|
|
return iNumCreditsPaid;
|
|
}
|
|
|
|
|
|
int GetCreditsRequiredToPlayStyle( const Style *style )
|
|
{
|
|
if( GAMESTATE->GetPremium() == PREMIUM_JOINT )
|
|
return 1;
|
|
|
|
switch( style->m_StyleType )
|
|
{
|
|
case ONE_PLAYER_ONE_SIDE:
|
|
return 1;
|
|
case TWO_PLAYERS_TWO_SIDES:
|
|
return 2;
|
|
case ONE_PLAYER_TWO_SIDES:
|
|
return (GAMESTATE->GetPremium() == PREMIUM_DOUBLES) ? 1 : 2;
|
|
default:
|
|
ASSERT(0);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static bool AreStyleAndPlayModeCompatible( const Style *style, PlayMode pm )
|
|
{
|
|
if( style == NULL || pm == PLAY_MODE_INVALID )
|
|
return true;
|
|
|
|
switch( pm )
|
|
{
|
|
case PLAY_MODE_BATTLE:
|
|
case PLAY_MODE_RAVE:
|
|
// Can't play rave if there isn't enough room for two players.
|
|
// This is correct for dance (ie, no rave for solo and doubles),
|
|
// and should be okay for pump .. not sure about other game types.
|
|
// Techno Motion scales down versus arrows, though, so allow this.
|
|
if( style->m_iColsPerPlayer >= 6 && CString(GAMESTATE->m_pCurGame->m_szName) != "techno" )
|
|
return false;
|
|
|
|
/* Don't allow battle modes if the style takes both sides. */
|
|
if( style->m_StyleType==ONE_PLAYER_TWO_SIDES )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GameCommand::IsPlayable( CString *why ) const
|
|
{
|
|
if( m_bInvalid )
|
|
{
|
|
if( why )
|
|
*why = m_sInvalidReason;
|
|
return false;
|
|
}
|
|
|
|
if ( m_pStyle )
|
|
{
|
|
int iCredits = GAMESTATE->m_iCoins / PREFSMAN->m_iCoinsPerCredit;
|
|
const int iNumCreditsPaid = GetNumCreditsPaid();
|
|
const int iNumCreditsRequired = GetCreditsRequiredToPlayStyle(m_pStyle);
|
|
|
|
switch( GAMESTATE->GetCoinMode() )
|
|
{
|
|
case COIN_HOME:
|
|
case COIN_FREE:
|
|
iCredits = NUM_PLAYERS; /* not iNumCreditsPaid */
|
|
}
|
|
|
|
/* With PREFSMAN->m_bDelayedCreditsReconcile disabled, enough credits must be
|
|
* paid. (This means that enough sides must be joined.) Enabled, simply having
|
|
* enough credits lying in the machine is sufficient; we'll deduct the extra in
|
|
* Apply(). */
|
|
int iNumCreditsAvailable = iNumCreditsPaid;
|
|
if( PREFSMAN->m_bDelayedCreditsReconcile )
|
|
iNumCreditsAvailable += iCredits;
|
|
|
|
if( iNumCreditsAvailable < iNumCreditsRequired )
|
|
{
|
|
if( why )
|
|
*why = ssprintf( "need %i credits, have %i", iNumCreditsRequired, iNumCreditsAvailable );
|
|
return false;
|
|
}
|
|
|
|
/* If you've paid too much already, don't allow the mode. (If we allow this,
|
|
* the credits will be "refunded" in Apply(), but that's confusing.) */
|
|
/* Do allow the mode if they've already joined in more credits than
|
|
* are required. Otherwise, people who put in two credits to play
|
|
* doubles on a doubles-premium machiune will get locked out.
|
|
* the refund logic isn't that awkward because you never see the
|
|
* credits number jump up - the credits display is hidden if both
|
|
* sides are joined. -Chris */
|
|
//if( PREFSMAN->m_iCoinMode == COIN_PAY && iNumCreditsPaid > iNumCreditsRequired )
|
|
//{
|
|
// if( why )
|
|
// *why = ssprintf( "too many credits paid (%i > %i)", iNumCreditsPaid, iNumCreditsRequired );
|
|
// return false;
|
|
//}
|
|
|
|
/* If both sides are joined, disallow singles modes, since easy to select them
|
|
* accidentally, instead of versus mode. */
|
|
if( m_pStyle->m_StyleType == ONE_PLAYER_ONE_SIDE &&
|
|
GAMESTATE->GetNumSidesJoined() > 1 )
|
|
{
|
|
if( why )
|
|
*why = "too many players joined for ONE_PLAYER_ONE_CREDIT";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Don't allow a PlayMode that's incompatible with our current Style (if set),
|
|
* and vice versa. */
|
|
if( m_pm != PLAY_MODE_INVALID || m_pStyle != NULL )
|
|
{
|
|
const PlayMode pm = (m_pm != PLAY_MODE_INVALID) ? m_pm : GAMESTATE->m_PlayMode;
|
|
const Style *style = (m_pStyle != NULL)? m_pStyle: GAMESTATE->m_pCurStyle;
|
|
if( !AreStyleAndPlayModeCompatible( style, pm ) )
|
|
{
|
|
if( why )
|
|
*why = ssprintf("mode %s is incompatible with style %s",
|
|
PlayModeToString(pm).c_str(), style->m_szName );
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( !m_sScreen.CompareNoCase("ScreenEditCoursesMenu") )
|
|
{
|
|
vector<Course*> vCourses;
|
|
SONGMAN->GetAllCourses( vCourses, false );
|
|
|
|
if( vCourses.size() == 0 )
|
|
{
|
|
if( why )
|
|
*why = "No courses are installed";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( !m_sScreen.CompareNoCase("ScreenJukeboxMenu") ||
|
|
!m_sScreen.CompareNoCase("ScreenEditMenu") ||
|
|
!m_sScreen.CompareNoCase("ScreenEditCoursesMenu") )
|
|
{
|
|
if( SONGMAN->GetNumSongs() == 0 )
|
|
{
|
|
if( why )
|
|
*why = "No songs are installed";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( !m_sModifiers.empty() )
|
|
{
|
|
// TODO: Split this and check each modifier individually
|
|
if( UNLOCKMAN->ModifierIsLocked(m_sModifiers) )
|
|
{ if( why )
|
|
*why = "Modifier is locked";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameCommand::ApplyToAllPlayers() const
|
|
{
|
|
vector<PlayerNumber> vpns;
|
|
|
|
FOREACH_PlayerNumber( pn )
|
|
vpns.push_back( pn );
|
|
|
|
Apply( vpns );
|
|
}
|
|
|
|
void GameCommand::Apply( PlayerNumber pn ) const
|
|
{
|
|
vector<PlayerNumber> vpns;
|
|
vpns.push_back( pn );
|
|
Apply( vpns );
|
|
}
|
|
|
|
static HighScore MakeRandomHighScore( float fPercentDP )
|
|
{
|
|
HighScore hs;
|
|
hs.sName = "FAKE";
|
|
hs.grade = (Grade)SCALE( rand()%5, 0, 4, GRADE_TIER01, GRADE_TIER05 );
|
|
hs.iScore = rand()%100*1000;
|
|
hs.fPercentDP = fPercentDP;
|
|
hs.fSurviveSeconds = randomf( 30.0f, 100.0f );
|
|
PlayerOptions po;
|
|
po.ChooseRandomMofifiers();
|
|
hs.sModifiers = po.GetString();
|
|
hs.dateTime = DateTime::GetNowDateTime();
|
|
hs.sPlayerGuid = Profile::MakeGuid();
|
|
hs.sMachineGuid = Profile::MakeGuid();
|
|
hs.iProductID = rand()%10;
|
|
FOREACH_TapNoteScore( tns )
|
|
hs.iTapNoteScores[tns] = rand() % 100;
|
|
FOREACH_HoldNoteScore( hns )
|
|
hs.iHoldNoteScores[hns] = rand() % 100;
|
|
FOREACH_RadarCategory( rc )
|
|
hs.radarValues.m_Values.f[rc] = randomf( 0, 1 );
|
|
|
|
return hs;
|
|
}
|
|
|
|
/* Hack: if this GameCommand would set the screen, clear the setting and return
|
|
* the screen that would have been set. */
|
|
CString GameCommand::GetAndClearScreen()
|
|
{
|
|
CString sRet;
|
|
FOREACH( Command, m_Commands.v, cmd )
|
|
{
|
|
GameCommand gc;
|
|
gc.m_bInvalid = false;
|
|
gc.LoadOne( *cmd );
|
|
if( gc.m_sScreen != "" )
|
|
{
|
|
sRet = gc.m_sScreen;
|
|
m_Commands.v.erase( cmd );
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_sScreen = "";
|
|
|
|
return sRet;
|
|
}
|
|
|
|
void GameCommand::Apply( const vector<PlayerNumber> &vpns ) const
|
|
{
|
|
if( m_Commands.v.size() )
|
|
{
|
|
// We were filled using a GameCommand from metrics. Apply the options in order.
|
|
FOREACH_CONST( Command, m_Commands.v, cmd )
|
|
{
|
|
GameCommand gc;
|
|
gc.m_bInvalid = false;
|
|
gc.LoadOne( *cmd );
|
|
gc.ApplySelf( vpns );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We were filled by an OptionRowHandler in code. GameCommand isn't filled,
|
|
// so just apply the values that are already set in this.
|
|
this->ApplySelf( vpns );
|
|
}
|
|
}
|
|
|
|
void GameCommand::ApplySelf( const vector<PlayerNumber> &vpns ) const
|
|
{
|
|
const PlayMode OldPlayMode = GAMESTATE->m_PlayMode;
|
|
|
|
if( m_pGame != NULL )
|
|
GAMESTATE->m_pCurGame = m_pGame;
|
|
if( m_pm != PLAY_MODE_INVALID )
|
|
GAMESTATE->m_PlayMode = m_pm;
|
|
|
|
if( m_pStyle != NULL )
|
|
{
|
|
GAMESTATE->m_pCurStyle.Set( m_pStyle );
|
|
|
|
// It's possible to choose a style that didn't have enough
|
|
// players joined. If enough players aren't joined, then
|
|
// we need to subtract credits for the sides that will be
|
|
// joined as a result of applying this option.
|
|
if( PREFSMAN->m_CoinMode == COIN_PAY )
|
|
{
|
|
int iNumCreditsRequired = GetCreditsRequiredToPlayStyle(m_pStyle);
|
|
int iNumCreditsPaid = GetNumCreditsPaid();
|
|
int iNumCreditsOwed = iNumCreditsRequired - iNumCreditsPaid;
|
|
GAMESTATE->m_iCoins -= iNumCreditsOwed * PREFSMAN->m_iCoinsPerCredit;
|
|
LOG->Trace( "Deducted %i coins, %i remaining",
|
|
iNumCreditsOwed * PREFSMAN->m_iCoinsPerCredit, GAMESTATE->m_iCoins );
|
|
}
|
|
|
|
|
|
// If only one side is joined and we picked a style
|
|
// that requires both sides, join the other side.
|
|
switch( m_pStyle->m_StyleType )
|
|
{
|
|
case ONE_PLAYER_ONE_SIDE:
|
|
break;
|
|
case TWO_PLAYERS_TWO_SIDES:
|
|
case ONE_PLAYER_TWO_SIDES:
|
|
{
|
|
FOREACH_PlayerNumber( p )
|
|
GAMESTATE->m_bSideIsJoined[p] = true;
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
if( m_dc != DIFFICULTY_INVALID )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->ChangePreferredDifficulty( *pn, m_dc );
|
|
if( m_sAnnouncer != "" )
|
|
ANNOUNCER->SwitchAnnouncer( m_sAnnouncer );
|
|
if( m_sModifiers != "" )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->ApplyModifiers( *pn, m_sModifiers );
|
|
if( m_LuaFunction.IsSet() )
|
|
{
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
{
|
|
m_LuaFunction.PushSelf( LUA->L );
|
|
ASSERT( !lua_isnil(LUA->L, -1) );
|
|
|
|
lua_pushnumber( LUA->L, *pn ); // 1st parameter
|
|
lua_call( LUA->L, 1, 0 ); // call function with 1 argument and 0 results
|
|
}
|
|
}
|
|
if( m_sScreen != "" )
|
|
SCREENMAN->SetNewScreen( m_sScreen );
|
|
if( m_pSong )
|
|
{
|
|
GAMESTATE->m_pCurSong.Set( m_pSong );
|
|
GAMESTATE->m_pPreferredSong = m_pSong;
|
|
}
|
|
if( m_pSteps )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->m_pCurSteps[*pn].Set( m_pSteps );
|
|
if( m_pCourse )
|
|
{
|
|
GAMESTATE->m_pCurCourse = m_pCourse;
|
|
GAMESTATE->m_pPreferredCourse = m_pCourse;
|
|
}
|
|
if( m_pTrail )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->m_pCurTrail[*pn] = m_pTrail;
|
|
if( m_CourseDifficulty != DIFFICULTY_INVALID )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->ChangePreferredCourseDifficulty( *pn, m_CourseDifficulty );
|
|
if( m_pCharacter )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
GAMESTATE->m_pCurCharacters[*pn] = m_pCharacter;
|
|
for( map<CString,CString>::const_iterator i = m_SetEnv.begin(); i != m_SetEnv.end(); i++ )
|
|
GAMESTATE->m_mapEnv[ i->first ] = i->second;
|
|
if( !m_sSongGroup.empty() )
|
|
GAMESTATE->m_sPreferredSongGroup = m_sSongGroup;
|
|
if( m_SortOrder != SORT_INVALID )
|
|
GAMESTATE->m_PreferredSortOrder = m_SortOrder;
|
|
if( m_iUnlockIndex != -1 )
|
|
UNLOCKMAN->UnlockCode( m_iUnlockIndex );
|
|
if( m_sSoundPath != "" )
|
|
SOUND->PlayOnce( THEME->GetPathToS( m_sSoundPath ) );
|
|
if( m_iWeightPounds != -1 )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
PROFILEMAN->GetProfile(*pn)->m_iWeightPounds = m_iWeightPounds;
|
|
if( m_iGoalCalories != -1 )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
PROFILEMAN->GetProfile(*pn)->m_iGoalCalories = m_iGoalCalories;
|
|
if( m_GoalType != GOAL_INVALID )
|
|
FOREACH_CONST( PlayerNumber, vpns, pn )
|
|
PROFILEMAN->GetProfile(*pn)->m_GoalType = m_GoalType;
|
|
|
|
/* If we're going to stop music, do so before preparing new screens, so we don't
|
|
* stop music between preparing screens and loading screens. */
|
|
if( m_bStopMusic )
|
|
SOUND->StopMusic();
|
|
|
|
FOREACH_CONST( CString, m_vsScreensToPrepare, s )
|
|
SCREENMAN->PrepareScreen( *s );
|
|
if( m_bDeletePreparedScreens )
|
|
SCREENMAN->DeletePreparedScreens();
|
|
|
|
if( m_bClearBookkeepingData )
|
|
{
|
|
BOOKKEEPER->ClearAll();
|
|
BOOKKEEPER->WriteToDisk();
|
|
SCREENMAN->SystemMessage( "Bookkeeping data cleared." );
|
|
}
|
|
if( m_bClearMachineStats )
|
|
{
|
|
Profile* pProfile = PROFILEMAN->GetMachineProfile();
|
|
// don't reset the Guid
|
|
CString sGuid = pProfile->m_sGuid;
|
|
pProfile->InitAll();
|
|
pProfile->m_sGuid = sGuid;
|
|
PROFILEMAN->SaveMachineProfile();
|
|
SCREENMAN->SystemMessage( "Machine stats cleared." );
|
|
}
|
|
if( m_bClearMachineEdits )
|
|
{
|
|
int iNumAttempted = 0;
|
|
int iNumSuccessful = 0;
|
|
|
|
vector<CString> vsEditFiles;
|
|
GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_SUBDIR+"*.edit", vsEditFiles, false, true );
|
|
FOREACH_CONST( CString, vsEditFiles, i )
|
|
{
|
|
iNumAttempted++;
|
|
bool bSuccess = FILEMAN->Remove( *i );
|
|
if( bSuccess )
|
|
iNumSuccessful++;
|
|
}
|
|
|
|
// reload the machine profile
|
|
PROFILEMAN->SaveMachineProfile();
|
|
PROFILEMAN->LoadMachineProfile();
|
|
|
|
SCREENMAN->SystemMessage( ssprintf("%d edits cleared, %d errors.",iNumSuccessful,iNumAttempted-iNumSuccessful) );
|
|
}
|
|
if( m_bFillMachineStats )
|
|
{
|
|
// Choose a percent for all scores. This is useful for testing unlocks
|
|
// where some elements are unlocked at a certain percent complete
|
|
float fPercentDP = randomf( 0.6f, 1.2f );
|
|
CLAMP( fPercentDP, 0.0f, 1.0f );
|
|
|
|
Profile* pProfile = PROFILEMAN->GetMachineProfile();
|
|
|
|
vector<Song*> vpAllSongs = SONGMAN->GetAllSongs();
|
|
FOREACH( Song*, vpAllSongs, pSong )
|
|
{
|
|
vector<Steps*> vpAllSteps = (*pSong)->GetAllSteps();
|
|
FOREACH( Steps*, vpAllSteps, pSteps )
|
|
{
|
|
pProfile->IncrementStepsPlayCount( *pSong, *pSteps );
|
|
for( int i=0; i<PREFSMAN->m_iMaxHighScoresPerListForMachine; i++ )
|
|
{
|
|
int iIndex = 0;
|
|
pProfile->AddStepsHighScore( *pSong, *pSteps, MakeRandomHighScore(fPercentDP), iIndex );
|
|
}
|
|
}
|
|
}
|
|
|
|
vector<Course*> vpAllCourses;
|
|
SONGMAN->GetAllCourses( vpAllCourses, true );
|
|
FOREACH( Course*, vpAllCourses, pCourse )
|
|
{
|
|
vector<Trail*> vpAllTrails;
|
|
(*pCourse)->GetAllTrails( vpAllTrails );
|
|
FOREACH( Trail*, vpAllTrails, pTrail )
|
|
{
|
|
pProfile->IncrementCoursePlayCount( *pCourse, *pTrail );
|
|
for( int i=0; i<PREFSMAN->m_iMaxHighScoresPerListForMachine; i++ )
|
|
{
|
|
int iIndex = 0;
|
|
pProfile->AddCourseHighScore( *pCourse, *pTrail, MakeRandomHighScore(fPercentDP), iIndex );
|
|
}
|
|
}
|
|
}
|
|
|
|
PROFILEMAN->SaveMachineProfile();
|
|
SCREENMAN->SystemMessage( "Machine stats filled." );
|
|
}
|
|
if( m_bTransferStatsFromMachine )
|
|
{
|
|
bool bTriedToSave = false;
|
|
FOREACH_PlayerNumber( pn )
|
|
{
|
|
if( MEMCARDMAN->GetCardState(pn) != MEMORY_CARD_STATE_READY )
|
|
continue; // skip
|
|
|
|
MEMCARDMAN->MountCard(pn);
|
|
|
|
bTriedToSave = true;
|
|
|
|
CString sDir = MEM_CARD_MOUNT_POINT[pn];
|
|
sDir += "MachineProfile/";
|
|
|
|
bool bSaved = PROFILEMAN->GetMachineProfile()->SaveAllToDir( sDir, PREFSMAN->m_bSignProfileData );
|
|
|
|
MEMCARDMAN->UnmountCard(pn);
|
|
|
|
if( bSaved )
|
|
SCREENMAN->SystemMessage( ssprintf("Machine stats saved to P%d card.",pn+1) );
|
|
else
|
|
SCREENMAN->SystemMessage( ssprintf("Error saving machine stats to P%d card.",pn+1) );
|
|
break;
|
|
}
|
|
|
|
if( !bTriedToSave )
|
|
SCREENMAN->SystemMessage( "Stats not saved - No memory cards ready." );
|
|
|
|
MEMCARDMAN->FlushAndReset();
|
|
}
|
|
if( m_bTransferStatsToMachine )
|
|
{
|
|
bool bTriedToLoad = false;
|
|
FOREACH_PlayerNumber( pn )
|
|
{
|
|
if( MEMCARDMAN->GetCardState(pn) != MEMORY_CARD_STATE_READY )
|
|
continue; // skip
|
|
|
|
MEMCARDMAN->MountCard(pn);
|
|
|
|
bTriedToLoad = true;
|
|
|
|
CString sDir = MEM_CARD_MOUNT_POINT[pn];
|
|
sDir += "MachineProfile/";
|
|
|
|
Profile backup = *PROFILEMAN->GetMachineProfile();
|
|
|
|
Profile::LoadResult lr = PROFILEMAN->GetMachineProfile()->LoadAllFromDir( sDir, PREFSMAN->m_bSignProfileData );
|
|
switch( lr )
|
|
{
|
|
case Profile::success:
|
|
SCREENMAN->SystemMessage( ssprintf("Machine stats loaded from P%d card.",pn+1) );
|
|
break;
|
|
case Profile::failed_no_profile:
|
|
SCREENMAN->SystemMessage( ssprintf("There is no profile on P%d card.",pn+1) );
|
|
*PROFILEMAN->GetMachineProfile() = backup;
|
|
break;
|
|
case Profile::failed_tampered:
|
|
SCREENMAN->SystemMessage( ssprintf("The profile on P%d card contains corrupt or tampered data.",pn+1) );
|
|
*PROFILEMAN->GetMachineProfile() = backup;
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
MEMCARDMAN->UnmountCard(pn);
|
|
break;
|
|
}
|
|
|
|
if( !bTriedToLoad )
|
|
SCREENMAN->SystemMessage( "Stats not loaded - No memory cards ready." );
|
|
|
|
MEMCARDMAN->FlushAndReset();
|
|
}
|
|
if( m_bCopyEditsFromMachine )
|
|
{
|
|
bool bTriedToCopy = false;
|
|
FOREACH_PlayerNumber( pn )
|
|
{
|
|
if( MEMCARDMAN->GetCardState(pn) != MEMORY_CARD_STATE_READY )
|
|
continue; // skip
|
|
|
|
MEMCARDMAN->MountCard(pn);
|
|
|
|
bTriedToCopy = true;
|
|
|
|
CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR;
|
|
CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR;
|
|
|
|
int iNumAttempted = 0;
|
|
int iNumSuccessful = 0;
|
|
int iNumOverwritten = 0;
|
|
|
|
vector<CString> vsEditFiles;
|
|
GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false );
|
|
FOREACH_CONST( CString, vsEditFiles, i )
|
|
{
|
|
iNumAttempted++;
|
|
if( DoesFileExist(sToDir+*i) )
|
|
iNumOverwritten++;
|
|
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
|
if( bSuccess )
|
|
iNumSuccessful++;
|
|
}
|
|
|
|
MEMCARDMAN->UnmountCard(pn);
|
|
|
|
SCREENMAN->SystemMessage( ssprintf("Copied to P%d card: %d/%d copies OK (%d overwritten).",pn+1,iNumSuccessful,iNumAttempted,iNumOverwritten) );
|
|
break;
|
|
}
|
|
|
|
if( !bTriedToCopy )
|
|
SCREENMAN->SystemMessage( "Edits not copied - No memory cards ready." );
|
|
|
|
MEMCARDMAN->FlushAndReset();
|
|
}
|
|
if( m_bCopyEditsToMachine )
|
|
{
|
|
bool bTriedToCopy = false;
|
|
FOREACH_PlayerNumber( pn )
|
|
{
|
|
if( MEMCARDMAN->GetCardState(pn) != MEMORY_CARD_STATE_READY )
|
|
continue; // skip
|
|
|
|
MEMCARDMAN->MountCard(pn);
|
|
|
|
bTriedToCopy = true;
|
|
|
|
CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR;
|
|
CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR;
|
|
|
|
int iNumAttempted = 0;
|
|
int iNumSuccessful = 0;
|
|
int iNumOverwritten = 0;
|
|
|
|
vector<CString> vsEditFiles;
|
|
GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false );
|
|
FOREACH_CONST( CString, vsEditFiles, i )
|
|
{
|
|
iNumAttempted++;
|
|
if( DoesFileExist(sToDir+*i) )
|
|
iNumOverwritten++;
|
|
bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i );
|
|
if( bSuccess )
|
|
iNumSuccessful++;
|
|
}
|
|
|
|
MEMCARDMAN->UnmountCard(pn);
|
|
|
|
// reload the machine profile
|
|
PROFILEMAN->SaveMachineProfile();
|
|
PROFILEMAN->LoadMachineProfile();
|
|
|
|
SCREENMAN->SystemMessage( ssprintf("Copied from P%d card: %d/%d copies OK (%d overwritten).",pn+1,iNumSuccessful,iNumAttempted,iNumOverwritten) );
|
|
break;
|
|
}
|
|
|
|
if( !bTriedToCopy )
|
|
SCREENMAN->SystemMessage( "Edits not copied - No memory cards ready." );
|
|
|
|
MEMCARDMAN->FlushAndReset();
|
|
}
|
|
if( m_bInsertCredit )
|
|
{
|
|
InsertCredit();
|
|
}
|
|
if( m_bResetToFactoryDefaults )
|
|
{
|
|
PREFSMAN->ResetToFactoryDefaults();
|
|
SCREENMAN->SystemMessage( "All options reset to factory defaults." );
|
|
}
|
|
if( m_bApplyDefaultOptions )
|
|
{
|
|
FOREACH_PlayerNumber( p )
|
|
GAMESTATE->m_pPlayerState[p]->m_PlayerOptions.FromString( PREFSMAN->m_sDefaultModifiers );
|
|
GAMESTATE->m_SongOptions.FromString( PREFSMAN->m_sDefaultModifiers );
|
|
}
|
|
// HACK: Set life type to BATTERY just once here so it happens once and
|
|
// we don't override the user's changes if they back out.
|
|
if( GAMESTATE->m_PlayMode == PLAY_MODE_ONI &&
|
|
GAMESTATE->m_PlayMode != OldPlayMode &&
|
|
GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BAR )
|
|
{
|
|
GAMESTATE->m_SongOptions.m_LifeType = SongOptions::LIFE_BATTERY;
|
|
}
|
|
}
|
|
|
|
bool GameCommand::IsZero() const
|
|
{
|
|
if( m_pGame != NULL ||
|
|
m_pm != PLAY_MODE_INVALID ||
|
|
m_pStyle != NULL ||
|
|
m_dc != DIFFICULTY_INVALID ||
|
|
m_sAnnouncer != "" ||
|
|
m_sModifiers != "" ||
|
|
m_pSong != NULL ||
|
|
m_pSteps != NULL ||
|
|
m_pCourse != NULL ||
|
|
m_pTrail != NULL ||
|
|
m_pCharacter != NULL ||
|
|
m_CourseDifficulty != DIFFICULTY_INVALID ||
|
|
!m_sSongGroup.empty() ||
|
|
m_SortOrder != SORT_INVALID ||
|
|
m_iWeightPounds != -1 ||
|
|
m_iGoalCalories != -1 ||
|
|
m_GoalType != GOAL_INVALID
|
|
)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* (c) 2001-2004 Chris Danford, Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|