#include "global.h" #include "ScreenGameplay.h" #include "SongManager.h" #include "ScreenManager.h" #include "GameConstantsAndTypes.h" #include "PrefsManager.h" #include "GameManager.h" #include "Steps.h" #include "RageLog.h" #include "LifeMeter.h" #include "GameState.h" #include "ScoreDisplayNormal.h" #include "ScoreDisplayPercentage.h" #include "ScoreDisplayLifeTime.h" #include "ScoreDisplayOni.h" #include "ScoreDisplayRave.h" #include "ThemeManager.h" #include "RageTimer.h" #include "ScoreKeeperNormal.h" #include "ScoreKeeperRave.h" #include "LyricsLoader.h" #include "ActorUtil.h" #include "RageTextureManager.h" #include "GameSoundManager.h" #include "CombinedLifeMeterTug.h" #include "Inventory.h" #include "Course.h" #include "NoteDataUtil.h" #include "UnlockManager.h" #include "LightsManager.h" #include "ProfileManager.h" #include "StatsManager.h" #include "PlayerAI.h" // for NUM_SKILL_LEVELS #include "NetworkSyncManager.h" #include "Foreach.h" #include "DancingCharacters.h" #include "ScreenDimensions.h" #include "ThemeMetric.h" #include "PlayerState.h" #include "Style.h" #include "LuaManager.h" #include "MemoryCardManager.h" #include "CommonMetrics.h" #include "InputMapper.h" #include "Game.h" #include "ActiveAttackList.h" #include "Player.h" #include "DifficultyIcon.h" #include "DifficultyMeter.h" #include "PlayerScoreList.h" #include "InputEventPlus.h" #include "XmlFile.h" // // Defines // #define SHOW_LIFE_METER_FOR_DISABLED_PLAYERS THEME->GetMetricB(m_sName,"ShowLifeMeterForDisabledPlayers") #define EVAL_ON_FAIL THEME->GetMetricB(m_sName,"ShowEvaluationOnFail") #define SHOW_SCORE_IN_RAVE THEME->GetMetricB(m_sName,"ShowScoreInRave") #define SONG_POSITION_METER_WIDTH THEME->GetMetricF(m_sName,"SongPositionMeterWidth") #define PLAYER_X( sName, styleType ) THEME->GetMetricF(m_sName,ssprintf("Player%s%sX",sName.c_str(),StyleTypeToString(styleType).c_str())) #define STOP_COURSE_EARLY THEME->GetMetricB(m_sName,"StopCourseEarly") // evaluate this every time it's used static ThemeMetric INITIAL_BACKGROUND_BRIGHTNESS ("ScreenGameplay","InitialBackgroundBrightness"); static ThemeMetric SECONDS_BETWEEN_COMMENTS ("ScreenGameplay","SecondsBetweenComments"); AutoScreenMessage( SM_PlayGo ) // received while STATE_DANCING AutoScreenMessage( SM_LoadNextSong ) AutoScreenMessage( SM_StartLoadingNextSong ) // received while STATE_OUTRO AutoScreenMessage( SM_GoToScreenAfterBack ) // received while STATE_INTRO AutoScreenMessage( SM_StartHereWeGo ) AutoScreenMessage( SM_StopHereWeGo ) static Preference g_fNetStartOffset( "NetworkStartOffset", -3.0 ); PlayerInfo::PlayerInfo() { m_pn = PLAYER_INVALID; m_mp = MultiPlayer_INVALID; m_bIsDummy = false; m_pLifeMeter = NULL; m_ptextCourseSongNumber = NULL; m_ptextStepsDescription = NULL; m_pPrimaryScoreDisplay = NULL; m_pSecondaryScoreDisplay = NULL; m_pPrimaryScoreKeeper = NULL; m_pSecondaryScoreKeeper = NULL; m_ptextPlayerOptions = NULL; m_pActiveAttackList = NULL; m_pPlayer = NULL; m_pInventory = NULL; m_pDifficultyIcon = NULL; m_pDifficultyMeter = NULL; } void PlayerInfo::Load( PlayerNumber pn, MultiPlayer mp, bool bShowNoteField ) { m_pn = pn; m_mp = mp; m_bIsDummy = false; m_pLifeMeter = NULL; m_ptextCourseSongNumber = NULL; m_ptextStepsDescription = NULL; if( !IsMultiPlayer() ) { switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_REGULAR: case PLAY_MODE_NONSTOP: case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: if( PREFSMAN->m_bPercentageScoring ) m_pPrimaryScoreDisplay = new ScoreDisplayPercentage; else m_pPrimaryScoreDisplay = new ScoreDisplayNormal; break; case PLAY_MODE_ONI: case PLAY_MODE_ENDLESS: if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_TIME ) m_pPrimaryScoreDisplay = new ScoreDisplayLifeTime; else m_pPrimaryScoreDisplay = new ScoreDisplayOni; break; default: ASSERT(0); } } if( m_pPrimaryScoreDisplay ) m_pPrimaryScoreDisplay->Init( GetPlayerState(), GetPlayerStageStats() ); switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_RAVE: m_pSecondaryScoreDisplay = new ScoreDisplayRave; m_pSecondaryScoreDisplay->SetName( "ScoreDisplayRave" ); break; } if( m_pSecondaryScoreDisplay ) { m_pSecondaryScoreDisplay->Init( GetPlayerState(), GetPlayerStageStats() ); } switch( PREFSMAN->m_ScoringType ) { default: ASSERT(0); case PrefsManager::SCORING_NEW: case PrefsManager::SCORING_OLD: m_pPrimaryScoreKeeper = new ScoreKeeperNormal( GetPlayerState(), GetPlayerStageStats() ); break; } switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_RAVE: m_pSecondaryScoreKeeper = new ScoreKeeperRave( GetPlayerState(), GetPlayerStageStats() ); break; } switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: break; } m_ptextPlayerOptions = NULL; m_pActiveAttackList = NULL; m_pPlayer = new Player( bShowNoteField, true ); m_pInventory = NULL; m_pDifficultyIcon = NULL; m_pDifficultyMeter = NULL; if( IsMultiPlayer() ) { GetPlayerState()->m_CurrentPlayerOptions = GAMESTATE->m_pPlayerState[PLAYER_1]->m_CurrentPlayerOptions; GetPlayerState()->m_PlayerOptions = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions; GetPlayerState()->m_StagePlayerOptions = GAMESTATE->m_pPlayerState[PLAYER_1]->m_StagePlayerOptions; GetPlayerState()->m_StoredPlayerOptions = GAMESTATE->m_pPlayerState[PLAYER_1]->m_StoredPlayerOptions; } } void PlayerInfo::LoadDummyP1() { m_pn = PLAYER_1; m_bIsDummy = true; // don't init any of the scoring objects m_pPlayer = new Player( true, false ); m_PlayerStateDummy = *GAMESTATE->m_pPlayerState[PLAYER_1]; m_PlayerStateDummy.m_PlayerController = PC_AUTOPLAY; } PlayerInfo::~PlayerInfo() { SAFE_DELETE( m_pLifeMeter ); SAFE_DELETE( m_ptextCourseSongNumber ); SAFE_DELETE( m_ptextStepsDescription ); SAFE_DELETE( m_pPrimaryScoreDisplay ); SAFE_DELETE( m_pSecondaryScoreDisplay ); SAFE_DELETE( m_pPrimaryScoreKeeper ); SAFE_DELETE( m_pSecondaryScoreKeeper ); SAFE_DELETE( m_ptextPlayerOptions ); SAFE_DELETE( m_pActiveAttackList ); SAFE_DELETE( m_pPlayer ); SAFE_DELETE( m_pInventory ); SAFE_DELETE( m_pDifficultyIcon ); SAFE_DELETE( m_pDifficultyMeter ); } void PlayerInfo::ShowOniGameOver() { m_sprOniGameOver->PlayCommand( "Die" ); } PlayerState *PlayerInfo::GetPlayerState() { if( m_bIsDummy ) return &m_PlayerStateDummy; return IsMultiPlayer() ? GAMESTATE->m_pMultiPlayerState[ GetPlayerStateAndStageStatsIndex() ] : GAMESTATE->m_pPlayerState[ GetPlayerStateAndStageStatsIndex() ]; } PlayerStageStats *PlayerInfo::GetPlayerStageStats() { if( m_bIsDummy ) return &m_PlayerStageStatsDummy; return IsMultiPlayer() ? &STATSMAN->m_CurStageStats.m_multiPlayer[ GetPlayerStateAndStageStatsIndex() ] : &STATSMAN->m_CurStageStats.m_player[ GetPlayerStateAndStageStatsIndex() ]; } bool PlayerInfo::IsEnabled() { if( m_pn != PLAYER_INVALID ) return GAMESTATE->IsPlayerEnabled( m_pn ); if( m_mp != MultiPlayer_INVALID ) return GAMESTATE->IsMultiPlayerEnabled( m_mp ); else if( m_bIsDummy ) return true; ASSERT( 0 ); return false; } vector::iterator GetNextEnabledPlayerInfo( vector::iterator iter, vector &v ) { iter++; for( ; iter != v.end(); iter++ ) { if( !iter->IsEnabled() ) continue; return iter; } return iter; } vector::iterator GetNextEnabledPlayerInfoNotDummy( vector::iterator iter, vector &v ) { iter++; for( ; iter != v.end(); iter++ ) { if( iter->m_bIsDummy ) continue; if( !iter->IsEnabled() ) continue; return iter; } return iter; } vector::iterator GetNextEnabledPlayerNumberInfo( vector::iterator iter, vector &v ) { iter++; for( ; iter != v.end(); iter++ ) { if( iter->m_bIsDummy ) continue; if( !iter->IsEnabled() ) continue; if( iter->m_mp != MultiPlayer_INVALID ) continue; return iter; } return iter; } vector::iterator GetNextPlayerNumberInfo( vector::iterator iter, vector &v ) { iter++; for( ; iter != v.end(); iter++ ) { if( iter->m_bIsDummy ) continue; if( iter->m_pn == PLAYER_INVALID ) continue; return iter; } return iter; } vector::iterator GetNextVisiblePlayerInfo( vector::iterator iter, vector &v ) { iter++; for( ; iter != v.end(); iter++ ) { if( !iter->m_pPlayer->HasNoteField() ) continue; return iter; } return iter; } ScreenGameplay::ScreenGameplay() { m_pPlayerScoreList = NULL; } void ScreenGameplay::Init() { PLAYER_TYPE.Load( m_sName, "PlayerType" ); GIVE_UP_TEXT.Load( m_sName, "GiveUpText" ); GIVE_UP_ABORTED_TEXT.Load( m_sName, "GiveUpAbortedText" ); MUSIC_FADE_OUT_SECONDS.Load( m_sName, "MusicFadeOutSeconds" ); START_GIVES_UP.Load( m_sName, "StartGivesUp" ); BACK_GIVES_UP.Load( m_sName, "BackGivesUp" ); GIVING_UP_GOES_TO_PREV_SCREEN.Load( m_sName, "GivingUpGoesToPrevScreen" ); GIVING_UP_GOES_TO_NEXT_SCREEN.Load( m_sName, "GivingUpGoesToNextScreen" ); FAIL_AFTER_30_MISSES.Load( m_sName, "FailAfter30Misses" ); USE_FORCED_MODIFIERS_IN_BEGINNER.Load( m_sName, "UseForcedModifiersInBeginner" ); FORCED_MODIFIERS_IN_BEGINNER.Load( m_sName, "ForcedModifiersInBeginner" ); this->FillPlayerInfo( m_vPlayerInfo ); ASSERT_M( !m_vPlayerInfo.empty(), "m_vPlayerInfo must be filled by FillPlayerInfo" ); ScreenWithMenuElements::Init(); /* Pause MEMCARDMAN. If a memory card is removed, we don't want to interrupt the * player by making a noise until the game finishes. */ if( !GAMESTATE->m_bDemonstrationOrJukebox ) MEMCARDMAN->PauseMountingThread(); if( GAMESTATE->m_bDemonstrationOrJukebox ) LIGHTSMAN->SetLightsMode( LIGHTSMODE_DEMONSTRATION ); else LIGHTSMAN->SetLightsMode( LIGHTSMODE_GAMEPLAY ); m_pSoundMusic = NULL; m_bPaused = false; m_pCombinedLifeMeter = NULL; if( GAMESTATE->m_pCurSong == NULL && GAMESTATE->m_pCurCourse == NULL ) return; // ScreenDemonstration will move us to the next screen. We just need to survive for one update without crashing. /* Save selected options before we change them. */ GAMESTATE->StoreSelectedOptions(); /* Save settings to the profile now. Don't do this on extra stages, since the * user doesn't have full control; saving would force profiles to DIFFICULTY_HARD * and save over their default modifiers every time someone got an extra stage. * Do this before course modifiers are set up. */ if( !GAMESTATE->IsExtraStage() && !GAMESTATE->IsExtraStage2() ) { FOREACH_HumanPlayer( pn ) GAMESTATE->SaveCurrentSettingsToProfile(pn); } /* Called once per stage (single song or single course). */ GAMESTATE->BeginStage(); // fill in difficulty of CPU players with that of the first human player FOREACH_PotentialCpuPlayer(p) GAMESTATE->m_pCurSteps[p].Set( GAMESTATE->m_pCurSteps[ GAMESTATE->GetFirstHumanPlayer() ] ); /* Increment the course play count. */ if( GAMESTATE->IsCourseMode() && !GAMESTATE->m_bDemonstrationOrJukebox ) FOREACH_EnabledPlayer(p) PROFILEMAN->IncrementCoursePlayCount( GAMESTATE->m_pCurCourse, GAMESTATE->m_pCurTrail[p], p ); STATSMAN->m_CurStageStats.playMode = GAMESTATE->m_PlayMode; STATSMAN->m_CurStageStats.pStyle = GAMESTATE->m_pCurStyle; /* Record combo rollover. */ FOREACH_EnabledPlayerInfoNotDummy( m_vPlayerInfo, pi ) pi->GetPlayerStageStats()->UpdateComboList( 0, true ); if( GAMESTATE->IsExtraStage() ) STATSMAN->m_CurStageStats.StageType = StageStats::STAGE_EXTRA; else if( GAMESTATE->IsExtraStage2() ) STATSMAN->m_CurStageStats.StageType = StageStats::STAGE_EXTRA2; else STATSMAN->m_CurStageStats.StageType = StageStats::STAGE_NORMAL; m_DancingState = STATE_INTRO; // Set this in LoadNextSong() //m_fTimeLeftBeforeDancingComment = SECONDS_BETWEEN_COMMENTS; m_bZeroDeltaOnNextUpdate = false; m_SongBackground.SetName( "SongBackground" ); m_SongBackground.SetDrawOrder( DRAW_ORDER_BEFORE_EVERYTHING ); ON_COMMAND( m_SongBackground ); this->AddChild( &m_SongBackground ); m_SongForeground.SetName( "SongForeground" ); m_SongForeground.SetDrawOrder( DRAW_ORDER_OVERLAY+1 ); // on top of the overlay, but under transitions ON_COMMAND( m_SongForeground ); this->AddChild( &m_SongForeground ); if( PREFSMAN->m_bShowBeginnerHelper ) { m_BeginnerHelper.SetDrawOrder( DRAW_ORDER_BEFORE_EVERYTHING ); m_BeginnerHelper.SetXY( SCREEN_CENTER_X, SCREEN_CENTER_Y ); this->AddChild( &m_BeginnerHelper ); } if( !GAMESTATE->m_bDemonstrationOrJukebox ) // only load if we're going to use it { m_Toasty.Load( THEME->GetPathB(m_sName,"toasty") ); this->AddChild( &m_Toasty ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { pi->m_pPlayer->SetName( ssprintf("Player%s", pi->GetName().c_str()) ); // If pi->m_pn is set, then the player will be visible. If not, then it's not // visible and don't bother setting its position. float fPlayerX = PLAYER_X( pi->GetName(), GAMESTATE->GetCurrentStyle()->m_StyleType ); /* Perhaps this should be handled better by defining a new * StyleType for ONE_PLAYER_ONE_CREDIT_AND_ONE_COMPUTER, * but for now just ignore Center1Player when it's Battle or Rave * Mode. This doesn't begin to address two-player solo (6 arrows) */ if( PREFSMAN->m_bCenter1Player && GAMESTATE->m_PlayMode != PLAY_MODE_BATTLE && GAMESTATE->m_PlayMode != PLAY_MODE_RAVE && GAMESTATE->GetCurrentStyle()->m_StyleType == ONE_PLAYER_ONE_SIDE ) fPlayerX = SCREEN_CENTER_X; pi->m_pPlayer->SetXY( fPlayerX, SCREEN_CENTER_Y ); this->AddChild( pi->m_pPlayer ); } FOREACH_EnabledPlayerInfoNotDummy( m_vPlayerInfo, pi ) { if( pi->m_pPlayer->HasNoteField() ) { pi->m_sprOniGameOver.Load( THEME->GetPathG(m_sName,"oni gameover") ); pi->m_sprOniGameOver->SetName( ssprintf("OniGameOver%s",pi->GetName().c_str()) ); SET_XY_AND_ON_COMMAND( pi->m_sprOniGameOver ); this->AddChild( pi->m_sprOniGameOver ); } } m_NextSong.Load( THEME->GetPathB(m_sName,"next course song") ); m_NextSong.SetDrawOrder( DRAW_ORDER_TRANSITIONS-1 ); this->AddChild( &m_NextSong ); m_SongFinished.SetDrawOrder( DRAW_ORDER_TRANSITIONS-1 ); m_SongFinished.Load( THEME->GetPathB(m_sName,"song finished") ); this->AddChild( &m_SongFinished ); bool bBattery = GAMESTATE->m_SongOptions.m_LifeType==SongOptions::LIFE_BATTERY; // // Add LifeFrame // m_sprLifeFrame.Load( THEME->GetPathG(m_sName,bBattery?"oni life frame":"life frame") ); m_sprLifeFrame->SetName( "LifeFrame" ); SET_XY( m_sprLifeFrame ); this->AddChild( m_sprLifeFrame ); // // Add score frame // m_sprScoreFrame.Load( THEME->GetPathG(m_sName,bBattery?"oni score frame":"score frame") ); m_sprScoreFrame.SetName( "ScoreFrame" ); SET_XY( m_sprScoreFrame ); this->AddChild( &m_sprScoreFrame ); // // Add combined life meter // switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: m_pCombinedLifeMeter = new CombinedLifeMeterTug; m_pCombinedLifeMeter->SetName( "CombinedLife" ); SET_XY( *m_pCombinedLifeMeter ); this->AddChild( m_pCombinedLifeMeter ); break; } // // Before the lifemeter loads, if Networking is required // we need to wait, so that there is no Dead On Start issues. // if you wait too long at the second checkpoint, you will // appear dead when you begin your game. // NSMAN->StartRequest(0); // // Add individual life meter // switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_REGULAR: case PLAY_MODE_ONI: case PLAY_MODE_NONSTOP: case PLAY_MODE_ENDLESS: FOREACH_PlayerNumberInfo( m_vPlayerInfo, pi ) { if( !GAMESTATE->IsPlayerEnabled(pi->m_pn) && !SHOW_LIFE_METER_FOR_DISABLED_PLAYERS ) continue; // skip pi->m_pLifeMeter = LifeMeter::MakeLifeMeter( GAMESTATE->m_SongOptions.m_LifeType ); pi->m_pLifeMeter->Load( pi->GetPlayerState(), pi->GetPlayerStageStats() ); pi->m_pLifeMeter->SetName( ssprintf("Life%s",pi->GetName().c_str()) ); SET_XY( pi->m_pLifeMeter ); this->AddChild( pi->m_pLifeMeter ); } break; case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: break; } m_bShowScoreboard = false; #if !defined(WITHOUT_NETWORKING) // Only used in SMLAN/SMOnline: if( NSMAN->useSMserver && !GAMESTATE->PlayerUsingBothSides() ) { m_bShowScoreboard = PREFSMAN->m_bEnableScoreboard.Get(); PlayerNumber pn = GAMESTATE->GetFirstDisabledPlayer(); if( pn != PLAYER_INVALID && m_bShowScoreboard ) { FOREACH_NSScoreBoardColumn( col ) { m_Scoreboard[col].LoadFromFont( THEME->GetPathF(m_sName,"scoreboard") ); m_Scoreboard[col].SetShadowLength( 0 ); m_Scoreboard[col].SetName( ssprintf("ScoreboardC%iP%i",col+1,pn+1) ); SET_XY( m_Scoreboard[col] ); m_Scoreboard[col].SetText( NSMAN->m_Scoreboard[col] ); m_Scoreboard[col].SetVertAlign( align_top ); this->AddChild( &m_Scoreboard[col] ); } } } #endif m_MaxCombo.LoadFromFont( THEME->GetPathF(m_sName,"max combo") ); m_MaxCombo.SetName( "MaxCombo" ); SET_XY( m_MaxCombo ); m_MaxCombo.SetText( ssprintf("%d", m_vPlayerInfo[0].GetPlayerStageStats()->iMaxCombo) ); // TODO: Make this work for both players this->AddChild( &m_MaxCombo ); if( GAMESTATE->m_bMultiplayer ) { vector vpPlayerState; vector vpPlayerStageStats; FOREACH_EnabledMultiPlayer( p ) { vpPlayerState.push_back( m_vPlayerInfo[p].GetPlayerState() ); vpPlayerStageStats.push_back( m_vPlayerInfo[p].GetPlayerStageStats() ); } m_pPlayerScoreList = new PlayerScoreList; m_pPlayerScoreList->Init( vpPlayerState, vpPlayerStageStats ); m_pPlayerScoreList->SetName( "PlayerScoreList" ); SET_XY( m_pPlayerScoreList ); this->AddChild( m_pPlayerScoreList ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { // primary score display if( pi->m_pPrimaryScoreDisplay ) { pi->m_pPrimaryScoreDisplay->SetName( ssprintf("Score%s",pi->GetName().c_str()) ); SET_XY( pi->m_pPrimaryScoreDisplay ); if( GAMESTATE->m_PlayMode != PLAY_MODE_RAVE || SHOW_SCORE_IN_RAVE ) /* XXX: ugly */ this->AddChild( pi->m_pPrimaryScoreDisplay ); } // secondary score display if( pi->m_pSecondaryScoreDisplay ) { pi->m_pSecondaryScoreDisplay->SetName( ssprintf("SecondaryScore%s",pi->GetName().c_str()) ); SET_XY( pi->m_pSecondaryScoreDisplay ); this->AddChild( pi->m_pSecondaryScoreDisplay ); } } // // Add stage / SongNumber // m_sprCourseSongNumber.SetName( "CourseSongNumber" ); SET_XY( m_sprCourseSongNumber ); if( GAMESTATE->IsCourseMode() ) this->AddChild( &m_sprCourseSongNumber ); FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { if( GAMESTATE->IsCourseMode() ) { ASSERT( pi->m_ptextCourseSongNumber == NULL ); pi->m_ptextCourseSongNumber = new BitmapText; pi->m_ptextCourseSongNumber->LoadFromFont( THEME->GetPathF(m_sName,"song num") ); pi->m_ptextCourseSongNumber->SetShadowLength( 0 ); pi->m_ptextCourseSongNumber->SetName( ssprintf("SongNumber%s",pi->GetName().c_str()) ); SET_XY( pi->m_ptextCourseSongNumber ); pi->m_ptextCourseSongNumber->SetText( "" ); pi->m_ptextCourseSongNumber->SetDiffuse( RageColor(0,0.5f,1,1) ); // light blue this->AddChild( pi->m_ptextCourseSongNumber ); } ASSERT( pi->m_ptextStepsDescription == NULL ); pi->m_ptextStepsDescription = new BitmapText; pi->m_ptextStepsDescription->LoadFromFont( THEME->GetPathF(m_sName,"StepsDescription") ); pi->m_ptextStepsDescription->SetName( ssprintf("StepsDescription%s",pi->GetName().c_str()) ); SET_XY( pi->m_ptextStepsDescription ); this->AddChild( pi->m_ptextStepsDescription ); // // Player/Song options // ASSERT( pi->m_ptextPlayerOptions == NULL ); pi->m_ptextPlayerOptions = new BitmapText; pi->m_ptextPlayerOptions->LoadFromFont( THEME->GetPathF(m_sName,"player options") ); pi->m_ptextPlayerOptions->SetShadowLength( 0 ); pi->m_ptextPlayerOptions->SetName( ssprintf("PlayerOptions%s",pi->GetName().c_str()) ); SET_XY( pi->m_ptextPlayerOptions ); this->AddChild( pi->m_ptextPlayerOptions ); // // Difficulty icon and meter // ASSERT( pi->m_pDifficultyIcon == NULL ); pi->m_pDifficultyIcon = new DifficultyIcon; pi->m_pDifficultyIcon->Load( THEME->GetPathG(m_sName,ssprintf("difficulty icons %dx%d",NUM_PLAYERS,NUM_Difficulty)) ); /* Position it in LoadNextSong. */ this->AddChild( pi->m_pDifficultyIcon ); ASSERT( pi->m_pDifficultyMeter == NULL ); pi->m_pDifficultyMeter = new DifficultyMeter; pi->m_pDifficultyMeter->Load( m_sName + ssprintf(" DifficultyMeter%s",pi->GetName().c_str()) ); /* Position it in LoadNextSong. */ this->AddChild( pi->m_pDifficultyMeter ); // switch( GAMESTATE->m_PlayMode ) // { // case PLAY_MODE_BATTLE: // pi->m_pInventory = new Inventory; // pi->m_pInventory->Load( p ); // this->AddChild( pi->m_pInventory ); // break; // } } m_textSongOptions.LoadFromFont( THEME->GetPathF(m_sName,"song options") ); m_textSongOptions.SetShadowLength( 0 ); m_textSongOptions.SetName( "SongOptions" ); SET_XY( m_textSongOptions ); m_textSongOptions.SetText( GAMESTATE->m_SongOptions.GetString() ); this->AddChild( &m_textSongOptions ); FOREACH_VisiblePlayerInfo( m_vPlayerInfo, pi ) { ASSERT( pi->m_pActiveAttackList == NULL ); pi->m_pActiveAttackList = new ActiveAttackList; pi->m_pActiveAttackList->LoadFromFont( THEME->GetPathF(m_sName,"ActiveAttackList") ); pi->m_pActiveAttackList->Init( pi->GetPlayerState() ); pi->m_pActiveAttackList->SetName( ssprintf("ActiveAttackList%s",pi->GetName().c_str()) ); SET_XY( pi->m_pActiveAttackList ); this->AddChild( pi->m_pActiveAttackList ); } if( PREFSMAN->m_bShowLyrics ) this->AddChild( &m_LyricDisplay ); m_BPMDisplay.SetName( "BPMDisplay" ); m_BPMDisplay.Load(); SET_XY( m_BPMDisplay ); this->AddChild( &m_BPMDisplay ); m_fLastBPS = 0; if( !GAMESTATE->m_bDemonstrationOrJukebox ) // only load if we're going to use it { m_Ready.Load( THEME->GetPathB(m_sName,"ready") ); this->AddChild( &m_Ready ); m_Go.Load( THEME->GetPathB(m_sName,"go") ); this->AddChild( &m_Go ); m_Failed.Load( THEME->GetPathB(m_sName,"failed") ); m_Failed.SetDrawOrder( DRAW_ORDER_TRANSITIONS-1 ); // on top of everything else this->AddChild( &m_Failed ); m_textDebug.LoadFromFont( THEME->GetPathF("Common","normal") ); m_textDebug.SetName( "Debug" ); SET_XY( m_textDebug ); m_textDebug.SetDrawOrder( DRAW_ORDER_TRANSITIONS-1 ); // just under transitions, over the foreground this->AddChild( &m_textDebug ); m_soundAssistTick.Load( THEME->GetPathS(m_sName,"assist tick"), true ); if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() ) // only load if we're going to use it { m_textSurviveTime.LoadFromFont( THEME->GetPathF(m_sName,"survive time") ); m_textSurviveTime.SetShadowLength( 0 ); m_textSurviveTime.SetName( "SurviveTime" ); SET_XY( m_textSurviveTime ); m_textSurviveTime.SetDrawOrder( DRAW_ORDER_TRANSITIONS-1 ); m_textSurviveTime.SetDiffuse( RageColor(1,1,1,0) ); this->AddChild( &m_textSurviveTime ); } switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_BATTLE: m_soundBattleTrickLevel1.Load( THEME->GetPathS(m_sName,"battle trick level1"), true ); m_soundBattleTrickLevel2.Load( THEME->GetPathS(m_sName,"battle trick level2"), true ); m_soundBattleTrickLevel3.Load( THEME->GetPathS(m_sName,"battle trick level3"), true ); break; } } m_SongBackground.Init(); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { pi->m_pPlayer->Init( PLAYER_TYPE, pi->GetPlayerState(), pi->GetPlayerStageStats(), pi->m_pLifeMeter, m_pCombinedLifeMeter, pi->m_pPrimaryScoreDisplay, pi->m_pSecondaryScoreDisplay, pi->m_pInventory, pi->m_pPrimaryScoreKeeper, pi->m_pSecondaryScoreKeeper ); } // // fill in m_apSongsQueue, m_vpStepsQueue, m_asModifiersQueue // InitSongQueues(); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { ASSERT( !pi->m_vpStepsQueue.empty() ); if( pi->m_pPrimaryScoreKeeper ) pi->m_pPrimaryScoreKeeper->Load( m_apSongsQueue, pi->m_vpStepsQueue, pi->m_asModifiersQueue ); if( pi->m_pSecondaryScoreKeeper ) pi->m_pSecondaryScoreKeeper->Load( m_apSongsQueue, pi->m_vpStepsQueue, pi->m_asModifiersQueue ); } GAMESTATE->m_bGameplayLeadIn.Set( true ); /* LoadNextSong first, since that positions some elements which need to be * positioned before we TweenOnScreen. */ LoadNextSong(); this->SortByDrawOrder(); m_GiveUpTimer.SetZero(); } // // fill in m_apSongsQueue, m_vpStepsQueue, m_asModifiersQueue // void ScreenGameplay::InitSongQueues() { LOG->Trace("InitSongQueues"); if( GAMESTATE->IsCourseMode() ) { Course* pCourse = GAMESTATE->m_pCurCourse; ASSERT( pCourse ); m_apSongsQueue.clear(); PlayerNumber pnMaster = GAMESTATE->m_MasterPlayerNumber; Trail *pTrail = GAMESTATE->m_pCurTrail[pnMaster]; ASSERT( pTrail ); FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e ) { ASSERT( e->pSong ); m_apSongsQueue.push_back( e->pSong ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { Trail *pTrail = GAMESTATE->m_pCurTrail[ pi->GetStepsAndTrailIndex() ]; ASSERT( pTrail ); pi->m_vpStepsQueue.clear(); pi->m_asModifiersQueue.clear(); FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e ) { ASSERT( e->pSteps ); pi->m_vpStepsQueue.push_back( e->pSteps ); AttackArray a; e->GetAttackArray( a ); pi->m_asModifiersQueue.push_back( a ); } // In a survival course, override stored mods if( pCourse->GetCourseType() == COURSE_TYPE_SURVIVAL ) { pi->GetPlayerState()->m_StagePlayerOptions.FromString( "clearall,"+CommonMetrics::DEFAULT_MODIFIERS.GetValue(), true ); pi->GetPlayerState()->RebuildPlayerOptionsFromActiveAttacks(); } } } else { m_apSongsQueue.push_back( GAMESTATE->m_pCurSong ); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { Steps *pSteps = GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ]; pi->m_vpStepsQueue.push_back( pSteps ); AttackArray aa; pi->m_asModifiersQueue.push_back( aa ); if( pSteps->GetDifficulty() == DIFFICULTY_BEGINNER && (bool)USE_FORCED_MODIFIERS_IN_BEGINNER ) { pi->GetPlayerState()->m_StagePlayerOptions.FromString( FORCED_MODIFIERS_IN_BEGINNER, true ); pi->GetPlayerState()->RebuildPlayerOptionsFromActiveAttacks(); } } } // Fill StageStats STATSMAN->m_CurStageStats.vpPossibleSongs = m_apSongsQueue; FOREACH( PlayerInfo, m_vPlayerInfo, pi ) { if( pi->GetPlayerStageStats() ) pi->GetPlayerStageStats()->vpPossibleSteps = pi->m_vpStepsQueue; } } ScreenGameplay::~ScreenGameplay() { if( this->IsFirstUpdate() ) { /* We never received any updates. That means we were deleted without being * used, and never actually played. (This can happen when backing out of * ScreenStage.) Cancel the stage. */ GAMESTATE->CancelStage(); } LOG->Trace( "ScreenGameplay::~ScreenGameplay()" ); SAFE_DELETE( m_pPlayerScoreList ); if( !GAMESTATE->m_bDemonstrationOrJukebox ) MEMCARDMAN->UnPauseMountingThread(); SAFE_DELETE( m_pCombinedLifeMeter ); if( m_pSoundMusic ) m_pSoundMusic->StopPlaying(); m_soundAssistTick.StopPlaying(); /* Stop any queued assist ticks. */ NSMAN->ReportSongOver(); } bool ScreenGameplay::IsLastSong() { if( GAMESTATE->m_pCurCourse && GAMESTATE->m_pCurCourse->m_bRepeat ) return false; return GAMESTATE->GetCourseSongIndex() >= (int)m_apSongsQueue.size()-1; // GetCourseSongIndex() is 0-based } void ScreenGameplay::SetupSong( int iSongIndex ) { FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { /* This is the first beat that can be changed without it being visible. Until * we draw for the first time, any beat can be changed. */ pi->GetPlayerState()->m_fLastDrawnBeat = -100; GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ].Set( pi->m_vpStepsQueue[iSongIndex] ); /* Load new NoteData into Player. Do this before * RebuildPlayerOptionsFromActiveAttacks or else transform mods will get * propogated to GAMESTATE->m_pPlayerOptions too early and be double-applied * to the NoteData: * once in Player::Load, then again in Player::ApplyActiveAttacks. This * is very bad for transforms like AddMines. */ NoteData originalNoteData; GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ]->GetNoteData( originalNoteData ); const Style* pStyle = GAMESTATE->GetCurrentStyle(); NoteData ndTransformed; pStyle->GetTransformedNoteDataForStyle( pi->GetStepsAndTrailIndex(), originalNoteData, ndTransformed ); // HACK: Apply NoteSkins from global course options. Do this before Player::Load, // since it needs to know which note skin to load. pi->GetPlayerState()->m_ModsToApply.clear(); for( unsigned i=0; im_asModifiersQueue[iSongIndex].size(); ++i ) { Attack a = pi->m_asModifiersQueue[iSongIndex][i]; if( a.fStartSecond != 0 ) continue; a.fStartSecond = -1; // now PlayerOptions po; po.FromString( a.sModifiers ); if( po.m_sNoteSkin.empty() ) continue; a.sModifiers = po.m_sNoteSkin; pi->GetPlayerState()->LaunchAttack( a ); /* Update attack bOn flags. */ pi->GetPlayerState()->Update(0); } // load player { NoteData nd = ndTransformed; NoteDataUtil::RemoveAllTapsOfType( nd, TapNote::autoKeysound ); pi->m_pPlayer->Load( nd ); } // load auto keysounds { NoteData nd = ndTransformed; NoteDataUtil::RemoveAllTapsExceptForType( nd, TapNote::autoKeysound ); m_AutoKeysounds.Load( pi->GetStepsAndTrailIndex(), nd ); } // Put course options into effect. Do this after Player::Load so // that mods aren't double-applied. pi->GetPlayerState()->m_ModsToApply.clear(); for( unsigned i=0; im_asModifiersQueue[iSongIndex].size(); ++i ) { Attack a = pi->m_asModifiersQueue[iSongIndex][i]; if( a.fStartSecond == 0 ) a.fStartSecond = -1; // now pi->GetPlayerState()->LaunchAttack( a ); GAMESTATE->m_SongOptions.FromString( a.sModifiers ); } /* Update attack bOn flags. */ pi->GetPlayerState()->Update(0); /* Hack: Course modifiers that are set to start immediately shouldn't tween on. */ pi->GetPlayerState()->m_CurrentPlayerOptions = pi->GetPlayerState()->m_PlayerOptions; } } void ScreenGameplay::LoadCourseSongNumber( int iSongNumber ) { if( !GAMESTATE->IsCourseMode() ) return; const RString path = THEME->GetPathG( m_sName, ssprintf("course song %i",iSongNumber+1), true ); if( path != "" ) m_sprCourseSongNumber.Load( path ); else m_sprCourseSongNumber.UnloadTexture(); SCREENMAN->ZeroNextUpdate(); } void ScreenGameplay::ReloadCurrentSong() { FOREACH_EnabledPlayerInfoNotDummy( m_vPlayerInfo, pi ) pi->GetPlayerStageStats()->iSongsPlayed--; LoadNextSong(); } void ScreenGameplay::LoadNextSong() { GAMESTATE->ResetMusicStatistics(); FOREACH_EnabledPlayerInfoNotDummy( m_vPlayerInfo, pi ) { pi->GetPlayerStageStats()->iSongsPlayed++; if( pi->m_ptextCourseSongNumber ) pi->m_ptextCourseSongNumber->SetText( ssprintf("%d", pi->GetPlayerStageStats()->iSongsPassed+1) ); } // super hack if( GAMESTATE->m_bMultiplayer ) STATSMAN->m_CurStageStats.m_player[GAMESTATE->m_MasterPlayerNumber].iSongsPlayed++; LoadCourseSongNumber( GAMESTATE->GetCourseSongIndex() ); int iPlaySongIndex = GAMESTATE->GetCourseSongIndex(); iPlaySongIndex %= m_apSongsQueue.size(); GAMESTATE->m_pCurSong.Set( m_apSongsQueue[iPlaySongIndex] ); STATSMAN->m_CurStageStats.vpPlayedSongs.push_back( GAMESTATE->m_pCurSong ); // No need to do this here. We do it in SongFinished(). //GAMESTATE->RemoveAllActiveAttacks(); GAMESTATE->RestoreStageOptions(); /* If we're in battery mode, force FailImmediate. We assume in PlayerMinus::Step that * failed players can't step. */ if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY ) GAMESTATE->m_SongOptions.m_FailType = SongOptions::FAIL_IMMEDIATE; m_textSongOptions.SetText( GAMESTATE->m_SongOptions.GetString() ); SetupSong( iPlaySongIndex ); Song* pSong = GAMESTATE->m_pCurSong; FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { Steps* pSteps = GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ]; pi->GetPlayerStageStats()->vpPlayedSteps.push_back( pSteps ); ASSERT( GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ] ); if( pi->m_ptextStepsDescription ) pi->m_ptextStepsDescription->SetText( pSteps->GetDescription() ); /* Increment the play count even if the player fails. (It's still popular, * even if the people playing it aren't good at it.) */ if( !GAMESTATE->m_bDemonstrationOrJukebox ) { if( pi->m_pn != PLAYER_INVALID ) PROFILEMAN->IncrementStepsPlayCount( pSong, pSteps, pi->m_pn ); } if( pi->m_ptextPlayerOptions ) pi->m_ptextPlayerOptions->SetText( pi->GetPlayerState()->m_PlayerOptions.GetString() ); if( pi->m_pActiveAttackList ) pi->m_pActiveAttackList->Refresh(); // reset oni game over graphic SET_XY_AND_ON_COMMAND( pi->m_sprOniGameOver ); if( GAMESTATE->m_SongOptions.m_LifeType==SongOptions::LIFE_BATTERY && pi->GetPlayerStageStats()->bFailed ) // already failed pi->ShowOniGameOver(); if( GAMESTATE->m_SongOptions.m_LifeType==SongOptions::LIFE_BAR && GAMESTATE->m_PlayMode == PLAY_MODE_REGULAR && !GAMESTATE->IsEventMode() && !GAMESTATE->m_bDemonstrationOrJukebox ) { if( pi->m_pLifeMeter ) pi->m_pLifeMeter->UpdateNonstopLifebar( GAMESTATE->GetStageIndex(), PREFSMAN->m_iSongsPerPlay, PREFSMAN->m_iProgressiveStageLifebar); } if( GAMESTATE->m_SongOptions.m_LifeType==SongOptions::LIFE_BAR && GAMESTATE->m_PlayMode == PLAY_MODE_NONSTOP ) { if( pi->m_pLifeMeter ) pi->m_pLifeMeter->UpdateNonstopLifebar( GAMESTATE->GetCourseSongIndex(), GAMESTATE->m_pCurCourse->GetEstimatedNumStages(), PREFSMAN->m_iProgressiveNonstopLifebar); } if( pi->m_pDifficultyIcon ) pi->m_pDifficultyIcon->SetFromSteps( pi->GetStepsAndTrailIndex(), pSteps ); if( pi->m_pDifficultyMeter ) { pi->m_pDifficultyMeter->SetName( m_sName + ssprintf(" DifficultyMeter%s",pi->GetName().c_str()) ); pi->m_pDifficultyMeter->SetFromSteps( pSteps ); } /* The actual note data for scoring is the base class of Player. This includes * transforms, like Wide. Otherwise, the scoring will operate on the wrong data. */ if( pi->m_pPrimaryScoreKeeper ) pi->m_pPrimaryScoreKeeper->OnNextSong( GAMESTATE->GetCourseSongIndex(), pSteps, &pi->m_pPlayer->m_NoteData ); if( pi->m_pSecondaryScoreKeeper ) pi->m_pSecondaryScoreKeeper->OnNextSong( GAMESTATE->GetCourseSongIndex(), pSteps, &pi->m_pPlayer->m_NoteData ); // Don't mess with the PlayerController of the Dummy player if( !pi->m_bIsDummy ) { if( GAMESTATE->m_bDemonstrationOrJukebox ) { pi->GetPlayerState()->m_PlayerController = PC_CPU; pi->GetPlayerState()->m_iCpuSkill = 5; } else if( GAMESTATE->IsCpuPlayer(pi->GetStepsAndTrailIndex()) ) { pi->GetPlayerState()->m_PlayerController = PC_CPU; int iMeter = pSteps->GetMeter(); int iNewSkill = SCALE( iMeter, MIN_METER, MAX_METER, 0, NUM_SKILL_LEVELS-1 ); /* Watch out: songs aren't actually bound by MAX_METER. */ iNewSkill = clamp( iNewSkill, 0, NUM_SKILL_LEVELS-1 ); pi->GetPlayerState()->m_iCpuSkill = iNewSkill; } else { pi->GetPlayerState()->m_PlayerController = PREFSMAN->m_AutoPlay; } } } bool bAllReverse = true; bool bAtLeastOneReverse = false; FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { if( pi->GetPlayerState()->m_PlayerOptions.m_fScrolls[PlayerOptions::SCROLL_REVERSE] == 1 ) bAtLeastOneReverse = true; else bAllReverse = false; } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { bool bReverse = pi->GetPlayerState()->m_PlayerOptions.m_fScrolls[PlayerOptions::SCROLL_REVERSE] == 1; if( pi->m_pDifficultyIcon ) { pi->m_pDifficultyIcon->SetName( ssprintf("Difficulty%s%s",pi->GetName().c_str(),bReverse?"Reverse":"") ); SET_XY( pi->m_pDifficultyIcon ); } if( pi->m_pDifficultyMeter ) { pi->m_pDifficultyMeter->SetName( ssprintf("DifficultyMeter%s%s",pi->GetName().c_str(),bReverse?"Reverse":"") ); SET_XY( pi->m_pDifficultyMeter ); } } /* XXX: We want to put the lyrics out of the way, but it's likely that one * player is in reverse and the other isn't. What to do? */ m_LyricDisplay.SetName( ssprintf( "Lyrics%s", bAllReverse? "Reverse": (bAtLeastOneReverse? "OneReverse": "")) ); SET_XY( m_LyricDisplay ); m_SongFinished.Reset(); // Load lyrics // XXX: don't load this here LyricsLoader LL; if( GAMESTATE->m_pCurSong->HasLyrics() ) LL.LoadFromLRCFile(GAMESTATE->m_pCurSong->GetLyricsPath(), *GAMESTATE->m_pCurSong); /* Set up song-specific graphics. */ // Check to see if any players are in beginner mode. // Note: steps can be different if turn modifiers are used. if( PREFSMAN->m_bShowBeginnerHelper ) { FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { Steps *pSteps = GAMESTATE->m_pCurSteps[ pi->GetStepsAndTrailIndex() ]; if( pSteps->GetDifficulty() == DIFFICULTY_BEGINNER ) m_BeginnerHelper.AddPlayer( pi->GetStepsAndTrailIndex(), &pi->m_pPlayer->m_NoteData ); } } m_SongBackground.Unload(); if( !PREFSMAN->m_bShowBeginnerHelper || !m_BeginnerHelper.Initialize(2) ) { m_BeginnerHelper.SetHidden( true ); /* BeginnerHelper disabled, or failed to load. */ m_SongBackground.LoadFromSong( GAMESTATE->m_pCurSong ); if( !GAMESTATE->m_bDemonstrationOrJukebox ) { /* This will fade from a preset brightness to the actual brightness (based * on prefs and "cover"). The preset brightness may be 0 (to fade from * black), or it might be 1, if the stage screen has the song BG and we're * coming from it (like Pump). This used to be done in SM_PlayReady, but * that means it's impossible to snap to the new brightness immediately. */ m_SongBackground.SetBrightness( INITIAL_BACKGROUND_BRIGHTNESS ); m_SongBackground.FadeToActualBrightness(); } } else { m_BeginnerHelper.SetHidden( false ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { if( !pi->GetPlayerStageStats()->bFailed ) { // give a little life back between stages if( pi->m_pLifeMeter ) pi->m_pLifeMeter->OnLoadSong(); if( pi->m_pPrimaryScoreDisplay ) pi->m_pPrimaryScoreDisplay->OnLoadSong(); if( pi->m_pSecondaryScoreDisplay ) pi->m_pSecondaryScoreDisplay->OnLoadSong(); } } if( m_pCombinedLifeMeter ) m_pCombinedLifeMeter->OnLoadSong(); m_SongForeground.LoadFromSong( GAMESTATE->m_pCurSong ); m_fTimeSinceLastDancingComment = 0; /* m_soundMusic and m_SongBackground take a very long time to load, * so cap fDelta at 0 so m_NextSong will show up on screen. * -Chris */ m_bZeroDeltaOnNextUpdate = true; SCREENMAN->ZeroNextUpdate(); // // Load cabinet lights data // LoadLights(); /* Load the music last, since it may start streaming and we don't want the music * to compete with other loading. */ m_AutoKeysounds.FinishLoading(); m_pSoundMusic = m_AutoKeysounds.GetSound(); } void ScreenGameplay::LoadLights() { if( !LIGHTSMAN->IsEnabled() ) return; // // First, check if the song has explicit lights // m_CabinetLightsNoteData.Init(); ASSERT( GAMESTATE->m_pCurSong ); const Steps *pSteps = GAMESTATE->m_pCurSong->GetClosestNotes( STEPS_TYPE_LIGHTS_CABINET, DIFFICULTY_MEDIUM ); if( pSteps != NULL ) { pSteps->GetNoteData( m_CabinetLightsNoteData ); return; } // // No explicit lights. Create autogen lights. // RString sDifficulty = PREFSMAN->m_sLightsStepsDifficulty; vector asDifficulties; split( sDifficulty, ",", asDifficulties ); Difficulty d1 = DIFFICULTY_INVALID; if( asDifficulties.size() > 0 ) d1 = StringToDifficulty( asDifficulties[0] ); pSteps = GAMESTATE->m_pCurSong->GetClosestNotes( GAMESTATE->GetCurrentStyle()->m_StepsType, d1 ); // If we can't find anything at all, stop. if( pSteps == NULL ) return; NoteData TapNoteData1; pSteps->GetNoteData( TapNoteData1 ); if( asDifficulties.size() > 1 ) { Difficulty d2 = StringToDifficulty( asDifficulties[1] ); const Steps *pSteps2 = GAMESTATE->m_pCurSong->GetClosestNotes( GAMESTATE->GetCurrentStyle()->m_StepsType, d2 ); if( pSteps != NULL && pSteps2 != NULL && pSteps != pSteps2 ) { NoteData TapNoteData2; pSteps2->GetNoteData( TapNoteData2 ); NoteDataUtil::LoadTransformedLightsFromTwo( TapNoteData1, TapNoteData2, m_CabinetLightsNoteData ); return; } /* fall through */ } NoteDataUtil::LoadTransformedLights( TapNoteData1, m_CabinetLightsNoteData, GameManager::StepsTypeToNumTracks(STEPS_TYPE_LIGHTS_CABINET) ); } float ScreenGameplay::StartPlayingSong(float MinTimeToNotes, float MinTimeToMusic) { ASSERT(MinTimeToNotes >= 0); ASSERT(MinTimeToMusic >= 0); /* XXX: We want the first beat *in use*, so we don't delay needlessly. */ const float fFirstBeat = GAMESTATE->m_pCurSong->m_fFirstBeat; const float fFirstSecond = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( fFirstBeat ); float fStartSecond = fFirstSecond - MinTimeToNotes; fStartSecond = min(fStartSecond, -MinTimeToMusic); RageSoundParams p; p.m_bAccurateSync = true; p.SetPlaybackRate( GAMESTATE->m_SongOptions.m_fMusicRate ); p.StopMode = RageSoundParams::M_CONTINUE; p.m_StartSecond = fStartSecond; ASSERT( !m_pSoundMusic->IsPlaying() ); m_pSoundMusic->Play( &p ); if( m_bPaused ) m_pSoundMusic->Pause( true ); /* Make sure GAMESTATE->m_fMusicSeconds is set up. */ GAMESTATE->m_fMusicSeconds = -5000; UpdateSongPosition(0); ASSERT( GAMESTATE->m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */ /* Return the amount of time until the first beat. */ return fFirstSecond - fStartSecond; } void ScreenGameplay::PauseGame( bool bPause, GameController gc ) { if( m_bPaused == bPause ) { LOG->Trace( "ScreenGameplay::PauseGame(%i) received, but already in that state; ignored", bPause ); return; } /* Don't pause if we're already tweening out. */ if( bPause && m_DancingState == STATE_OUTRO ) return; AbortGiveUp( false ); m_bPaused = bPause; m_PauseController = gc; m_pSoundMusic->Pause( bPause ); if( bPause ) this->PlayCommand( "Pause" ); else this->PlayCommand( "Unpause" ); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { pi->m_pPlayer->SetPaused( m_bPaused ); } } // play assist ticks void ScreenGameplay::PlayTicks() { if( !GAMESTATE->m_SongOptions.m_bAssistTick ) return; /* Sound cards have a latency between when a sample is Play()ed and when the sound * will start coming out the speaker. Compensate for this by boosting fPositionSeconds * ahead. This is just to make sure that we request the sound early enough for it to * come out on time; the actual precise timing is handled by SetStartTime. */ float fPositionSeconds = GAMESTATE->m_fMusicSeconds; fPositionSeconds += SOUND->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f; const float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ); const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) ); static int iRowLastCrossed = -1; if( iSongRow < iRowLastCrossed ) iRowLastCrossed = -1; int iTickRow = -1; // for each index we crossed since the last update: Player &player = *m_vPlayerInfo[0].m_pPlayer; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( player.m_NoteData, r, iRowLastCrossed+1, iSongRow+1 ) if( player.m_NoteData.IsThereATapOrHoldHeadAtRow( r ) ) iTickRow = r; iRowLastCrossed = iSongRow; if( iTickRow != -1 ) { const float fTickBeat = NoteRowToBeat( iTickRow ); const float fTickSecond = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( fTickBeat ); float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds; fSecondsUntil /= GAMESTATE->m_SongOptions.m_fMusicRate; /* 2x music rate means the time until the tick is halved */ RageSoundParams p; p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS); m_soundAssistTick.Play( &p ); } } /* Play announcer "type" if it's been at least fSeconds since the last announcer. */ void ScreenGameplay::PlayAnnouncer( RString type, float fSeconds ) { if( GAMESTATE->m_fOpponentHealthPercent == 0 ) return; // Shut the announcer up /* Don't play in demonstration. */ if( GAMESTATE->m_bDemonstrationOrJukebox ) return; /* Don't play before the first beat, or after we're finished. */ if( m_DancingState != STATE_DANCING ) return; if( GAMESTATE->m_pCurSong == NULL || // this will be true on ScreenDemonstration sometimes GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat ) return; if( m_fTimeSinceLastDancingComment < fSeconds ) return; m_fTimeSinceLastDancingComment = 0; SOUND->PlayOnceFromAnnouncer( type ); if( m_pCombinedLifeMeter ) m_pCombinedLifeMeter->OnTaunt(); } void ScreenGameplay::UpdateSongPosition( float fDeltaTime ) { if( !m_pSoundMusic->IsPlaying() ) return; RageTimer tm; const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm ); const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime ); GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_Timing, tm+fAdjust ); } void ScreenGameplay::BeginScreen() { ScreenWithMenuElements::BeginScreen(); if( GAMESTATE->m_pCurSong == NULL ) return; SOUND->PlayOnceFromAnnouncer( "gameplay intro" ); // crowd cheer // // Get the transitions rolling // if( GAMESTATE->m_bDemonstrationOrJukebox ) { StartPlayingSong( 0, 0 ); // *kick* (no transitions) } else if( NSMAN->useSMserver ) { //If we're using networking, we must not have any //delay. If we do this can cause inconsistancy //on different computers and differet themes StartPlayingSong( 0, 0 ); m_pSoundMusic->Stop(); float startOffset = g_fNetStartOffset; NSMAN->StartRequest(1); RageSoundParams p; p.m_bAccurateSync = true; p.SetPlaybackRate( 1.0 ); //Force 1.0 playback speed p.StopMode = RageSoundParams::M_CONTINUE; p.m_StartSecond = startOffset; m_pSoundMusic->Play( &p ); UpdateSongPosition(0); } else { float fMinTimeToMusic = m_In.GetLengthSeconds(); // start of m_Ready float fMinTimeToNotes = fMinTimeToMusic + m_Ready.GetLengthSeconds() + m_Go.GetLengthSeconds()+2; // end of Go /* * Tell the music to start, but don't actually make any noise for * at least 2.5 (or 1.5) seconds. (This is so we scroll on screen smoothly.) * * This is only a minimum: the music might be started later, to meet * the minimum-time-to-notes value. If you're writing song data, * and you want to make sure we get ideal timing here, make sure there's * a bit of space at the beginning of the music with no steps. */ /*float delay =*/ StartPlayingSong( fMinTimeToNotes, fMinTimeToMusic ); } } void ScreenGameplay::Update( float fDeltaTime ) { if( GAMESTATE->m_pCurSong == NULL ) { /* ScreenDemonstration will move us to the next screen. We just need to * survive for one update without crashing. We need to call Screen::Update * to make sure we receive the next-screen message. */ Screen::Update( fDeltaTime ); return; } UpdateSongPosition( fDeltaTime ); if( m_bZeroDeltaOnNextUpdate ) { Screen::Update( 0 ); m_bZeroDeltaOnNextUpdate = false; } else { Screen::Update( fDeltaTime ); } /* This happens if ScreenDemonstration::HandleScreenMessage sets a new screen when * PREFSMAN->m_bDelayedScreenLoad. */ if( GAMESTATE->m_pCurSong == NULL ) return; /* This can happen if ScreenDemonstration::HandleScreenMessage sets a new screen when * !PREFSMAN->m_bDelayedScreenLoad. (The new screen was loaded when we called Screen::Update, * and the ctor might set a new GAMESTATE->m_pCurSong, so the above check can fail.) */ if( SCREENMAN->GetTopScreen() != this ) return; /* Update actors when paused, but never move on to another state. */ if( m_bPaused ) return; if( GAMESTATE->m_MasterPlayerNumber != PLAYER_INVALID && !m_MaxCombo.GetHidden() ) m_MaxCombo.SetText( ssprintf("%d", STATSMAN->m_CurStageStats.m_player[GAMESTATE->m_MasterPlayerNumber].iMaxCombo) ); /* MAKE THIS WORK FOR BOTH PLAYERS! */ //LOG->Trace( "m_fOffsetInBeats = %f, m_fBeatsPerSecond = %f, m_Music.GetPositionSeconds = %f", m_fOffsetInBeats, m_fBeatsPerSecond, m_Music.GetPositionSeconds() ); m_AutoKeysounds.Update(fDeltaTime); // // update GameState HealthState // FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { PlayerNumber pn = pi->GetStepsAndTrailIndex(); if( (pi->m_pLifeMeter && pi->m_pLifeMeter->IsFailing()) || (m_pCombinedLifeMeter && m_pCombinedLifeMeter->IsFailing(pn)) ) { pi->GetPlayerState()->m_HealthState = PlayerState::DEAD; } else if( (pi->m_pLifeMeter && pi->m_pLifeMeter->IsHot()) || (m_pCombinedLifeMeter && m_pCombinedLifeMeter->IsHot(pn)) ) { pi->GetPlayerState()->m_HealthState = PlayerState::HOT; } else if( (pi->m_pLifeMeter && pi->m_pLifeMeter->IsInDanger()) || (m_pCombinedLifeMeter && m_pCombinedLifeMeter->IsInDanger(pn)) ) { pi->GetPlayerState()->m_HealthState = PlayerState::DANGER; } else { pi->GetPlayerState()->m_HealthState = PlayerState::ALIVE; } } switch( m_DancingState ) { case STATE_DANCING: /* Set STATSMAN->m_CurStageStats.bFailed for failed players. In, FAIL_IMMEDIATE, send * SM_BeginFailed if all players failed, and kill dead Oni players. */ FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { PlayerNumber pn = pi->GetStepsAndTrailIndex(); SongOptions::FailType ft = GAMESTATE->GetPlayerFailType( pi->GetPlayerState() ); SongOptions::LifeType lt = GAMESTATE->m_SongOptions.m_LifeType; if( ft == SongOptions::FAIL_OFF ) continue; // check for individual fail if( pi->m_pLifeMeter == NULL || !pi->m_pLifeMeter->IsFailing() ) continue; /* isn't failing */ if( m_pCombinedLifeMeter && !m_pCombinedLifeMeter->IsFailing(pn) ) continue; /* isn't failing */ if( pi->GetPlayerStageStats()->bFailed ) continue; /* failed and is already dead */ /* If recovery is enabled, only set fail if both are failing. * There's no way to recover mid-song in battery mode. */ if( lt != SongOptions::LIFE_BATTERY && PREFSMAN->m_bTwoPlayerRecovery && !GAMESTATE->AllAreDead() ) continue; LOG->Trace("Player %d failed", (int)pn); pi->GetPlayerStageStats()->bFailed = true; // fail // // Check for and do Oni die. // bool bAllowOniDie = false; switch( lt ) { case SongOptions::LIFE_BATTERY: bAllowOniDie = true; break; } if( bAllowOniDie && ft == SongOptions::FAIL_IMMEDIATE ) { if( !STATSMAN->m_CurStageStats.AllFailedEarlier() ) // if not the last one to fail { // kill them! SOUND->PlayOnceFromDir( THEME->GetPathS(m_sName,"oni die") ); pi->ShowOniGameOver(); pi->m_pPlayer->m_NoteData.Init(); // remove all notes and scoring pi->m_pPlayer->FadeToFail(); // tell the NoteField to fade to white } } } bool bAllFailed = true; FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { SongOptions::FailType ft = GAMESTATE->GetPlayerFailType( pi->GetPlayerState() ); switch( ft ) { case SongOptions::FAIL_IMMEDIATE: if( pi->GetPlayerState()->m_HealthState < PlayerState::DEAD ) bAllFailed = false; break; case SongOptions::FAIL_END_OF_SONG: bAllFailed = false; // wait until the end of the song to fail. break; case SongOptions::FAIL_OFF: bAllFailed = false; // never fail. break; default: ASSERT(0); } } if( bAllFailed ) SCREENMAN->PostMessageToTopScreen( SM_BeginFailed, 0 ); // // Update living players' alive time // FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) if( !pi->GetPlayerStageStats()->bFailed ) pi->GetPlayerStageStats()->fAliveSeconds += fDeltaTime * GAMESTATE->m_SongOptions.m_fMusicRate; // update fGameplaySeconds STATSMAN->m_CurStageStats.fGameplaySeconds += fDeltaTime; if( GAMESTATE->m_fSongBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat && GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat ) STATSMAN->m_CurStageStats.fStepsSeconds += fDeltaTime; // // Check for end of song // float fSecondsToStop = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat ); /* Make sure we keep going long enough to register a miss for the last note. */ Player &player = *m_vPlayerInfo[0].m_pPlayer; fSecondsToStop += player.GetMaxStepDistanceSeconds(); if( GAMESTATE->m_fMusicSeconds > fSecondsToStop && !m_SongFinished.IsTransitioning() && !m_NextSong.IsTransitioning() ) m_SongFinished.StartTransitioning( SM_NotesEnded ); // // update 2d dancing characters // FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { DancingCharacters *pCharacter = m_SongBackground.GetDancingCharacters(); if( pCharacter != NULL ) { TapNoteScore tns = pi->m_pPlayer->GetLastTapNoteScore(); ANIM_STATES_2D state = AS2D_MISS; switch( tns ) { case TNS_W4: case TNS_W3: state = AS2D_GOOD; break; case TNS_W2: case TNS_W1: state = AS2D_GREAT; break; default: state = AS2D_MISS; break; } if( state == AS2D_GREAT && pi->m_pLifeMeter && pi->m_pLifeMeter->GetLife() == 1.0f ) // full life state = AS2D_FEVER; pCharacter->Change2DAnimState( pi->m_pn, state ); } } // // Check for enemy death in enemy battle // static float fLastSeenEnemyHealth = 1; if( fLastSeenEnemyHealth != GAMESTATE->m_fOpponentHealthPercent ) { fLastSeenEnemyHealth = GAMESTATE->m_fOpponentHealthPercent; if( GAMESTATE->m_fOpponentHealthPercent == 0 ) { // HACK: Load incorrect directory on purpose for now. PlayAnnouncer( "gameplay battle damage level3", 0 ); GAMESTATE->RemoveAllActiveAttacks(); FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { if( !GAMESTATE->IsCpuPlayer(pi->m_pn) ) continue; SOUND->PlayOnceFromDir( THEME->GetPathS(m_sName,"oni die") ); pi->ShowOniGameOver(); pi->m_pPlayer->m_NoteData.Init(); // remove all notes and scoring pi->m_pPlayer->FadeToFail(); // tell the NoteField to fade to white } } } // // Check to see if it's time to play a ScreenGameplay comment // m_fTimeSinceLastDancingComment += fDeltaTime; switch( GAMESTATE->m_PlayMode ) { case PLAY_MODE_REGULAR: case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: if( GAMESTATE->OneIsHot() ) PlayAnnouncer( "gameplay comment hot", SECONDS_BETWEEN_COMMENTS ); else if( GAMESTATE->AllAreInDangerOrWorse() ) PlayAnnouncer( "gameplay comment danger", SECONDS_BETWEEN_COMMENTS ); else PlayAnnouncer( "gameplay comment good", SECONDS_BETWEEN_COMMENTS ); break; case PLAY_MODE_NONSTOP: case PLAY_MODE_ONI: case PLAY_MODE_ENDLESS: PlayAnnouncer( "gameplay comment oni", SECONDS_BETWEEN_COMMENTS ); break; default: ASSERT(0); } } // // update give up // bool bGiveUpTimerFired = !m_GiveUpTimer.IsZero() && m_GiveUpTimer.Ago() > 2.5f; if( bGiveUpTimerFired || (FAIL_AFTER_30_MISSES && GAMESTATE->AllHumanHaveComboOf30OrMoreMisses()) ) { // Give up STATSMAN->m_CurStageStats.bGaveUp = true; FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { pi->GetPlayerStageStats()->bFailed |= GAMESTATE->AllHumanHaveComboOf30OrMoreMisses(); } m_GiveUpTimer.SetZero(); if( GIVING_UP_GOES_TO_PREV_SCREEN ) { BeginBackingOutFromGameplay(); } else if( GIVING_UP_GOES_TO_NEXT_SCREEN ) { HandleScreenMessage( SM_LeaveGameplay ); return; } else { this->PostScreenMessage( SM_NotesEnded, 0 ); } return; } // // update bpm display // if( m_fLastBPS != GAMESTATE->m_fCurBPS && !m_BPMDisplay.GetHidden() ) { m_fLastBPS = GAMESTATE->m_fCurBPS; m_BPMDisplay.SetConstantBpm( GAMESTATE->m_fCurBPS * 60.0f ); } PlayTicks(); UpdateLights(); SendCrossedMessages(); if( NSMAN->useSMserver ) { FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) if( pi->m_pLifeMeter ) NSMAN->m_playerLife[pi->m_pn] = int(pi->m_pLifeMeter->GetLife()*10000); if( m_bShowScoreboard ) FOREACH_NSScoreBoardColumn(cn) if( m_bShowScoreboard && NSMAN->ChangedScoreboard(cn) ) m_Scoreboard[cn].SetText( NSMAN->m_Scoreboard[cn] ); } } void ScreenGameplay::UpdateLights() { if( !LIGHTSMAN->IsEnabled() ) return; if( m_CabinetLightsNoteData.GetNumTracks() == 0 ) // light data wasn't loaded return; const Style* pStyle = GAMESTATE->GetCurrentStyle(); bool bBlinkCabinetLight[NUM_CABINET_LIGHTS]; bool bBlinkGameButton[MAX_GAME_CONTROLLERS][MAX_GAME_BUTTONS]; ZERO( bBlinkCabinetLight ); ZERO( bBlinkGameButton ); bool bCrossedABeat = false; { const float fSongBeat = GAMESTATE->m_fLightSongBeat; const int iSongRow = BeatToNoteRowNotRounded( fSongBeat ); static int iRowLastCrossed = 0; float fBeatLast = roundf(NoteRowToBeat(iRowLastCrossed)); float fBeatNow = roundf(NoteRowToBeat(iSongRow)); bCrossedABeat = fBeatLast != fBeatNow; FOREACH_CabinetLight( cl ) { // for each index we crossed since the last update: FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( m_CabinetLightsNoteData, cl, r, iRowLastCrossed+1, iSongRow+1 ) { if( m_CabinetLightsNoteData.GetTapNote( cl, r ).type != TapNote::empty ) bBlinkCabinetLight[cl] = true; } if( m_CabinetLightsNoteData.IsHoldNoteAtRow( cl, iSongRow ) ) bBlinkCabinetLight[cl] = true; } FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { for( int t=0; tm_pPlayer->m_NoteData.GetNumTracks(); t++ ) { bool bBlink = false; // for each index we crossed since the last update: FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( pi->m_pPlayer->m_NoteData, t, r, iRowLastCrossed+1, iSongRow+1 ) { TapNote tn = pi->m_pPlayer->m_NoteData.GetTapNote(t,r); if( tn.type != TapNote::empty && tn.type != TapNote::mine ) bBlink = true; } // check if a hold should be active if( pi->m_pPlayer->m_NoteData.IsHoldNoteAtRow( t, iSongRow ) ) bBlink = true; if( bBlink ) { StyleInput si( pi->m_pn, t ); GameInput gi = pStyle->StyleInputToGameInput( si ); bBlinkGameButton[gi.controller][gi.button] = true; } } } iRowLastCrossed = iSongRow; } // Before the first beat of the song, all cabinet lights solid on (except for menu buttons). bool bOverrideCabinetBlink = (GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat); FOREACH_CabinetLight( cl ) { switch( cl ) { case LIGHT_BUTTONS_LEFT: case LIGHT_BUTTONS_RIGHT: // don't blink break; default: bBlinkCabinetLight[cl] |= bOverrideCabinetBlink; break; } } // Send blink data. FOREACH_CabinetLight( cl ) { if( bBlinkCabinetLight[cl] ) LIGHTSMAN->BlinkCabinetLight( cl ); } FOREACH_GameController( gc ) { FOREACH_GameButton( gb ) { if( bBlinkGameButton[gc][gb] ) LIGHTSMAN->BlinkGameButton( GameInput(gc,gb) ); } } } void ScreenGameplay::SendCrossedMessages() { { static int iRowLastCrossed = 0; float fPositionSeconds = GAMESTATE->m_fMusicSeconds; float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ); int iRowNow = BeatToNoteRowNotRounded( fSongBeat ); iRowNow = max( 0, iRowNow ); for( int r=iRowLastCrossed+1; r<=iRowNow; r++ ) { if( GetNoteType( r ) == NOTE_TYPE_4TH ) MESSAGEMAN->Broadcast( Message_BeatCrossed ); } iRowLastCrossed = iRowNow; } { const int NUM_MESSAGES_TO_SEND = 4; const float MESSAGE_SPACING_SECONDS = 0.5f; PlayerNumber pn = PLAYER_INVALID; FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { if( GAMESTATE->m_pCurSteps[ pi->m_pn ]->GetDifficulty() == DIFFICULTY_BEGINNER ) { pn = pi->m_pn; break; } } if( pn == PLAYER_INVALID ) return; NoteData &nd = m_vPlayerInfo[pn].m_pPlayer->m_NoteData; static int iRowLastCrossedAll[NUM_MESSAGES_TO_SEND] = { 0, 0, 0, 0 }; for( int i=0; im_fMusicSeconds + fOffsetFromCurrentSeconds; float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ); int iRowNow = BeatToNoteRowNotRounded( fSongBeat ); iRowNow = max( 0, iRowNow ); int &iRowLastCrossed = iRowLastCrossedAll[i]; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( nd, r, iRowLastCrossed+1, iRowNow+1 ) { int iNumTracksWithTapOrHoldHead = 0; for( int t=0; tGetCurrentGame(); RString sButton = pGame->ColToButtonName( t ); RString sMessageName = "NoteCrossed" + sButton; MESSAGEMAN->Broadcast( sMessageName ); } } if( iNumTracksWithTapOrHoldHead > 0 ) MESSAGEMAN->Broadcast( (Message)(Message_NoteCrossed + i) ); if( i == 0 && iNumTracksWithTapOrHoldHead >= 2 ) { RString sMessageName = "NoteCrossedJump"; MESSAGEMAN->Broadcast( sMessageName ); } } iRowLastCrossed = iRowNow; } } } void ScreenGameplay::BeginBackingOutFromGameplay() { m_DancingState = STATE_OUTRO; AbortGiveUp( false ); m_pSoundMusic->StopPlaying(); m_soundAssistTick.StopPlaying(); /* Stop any queued assist ticks. */ this->ClearMessageQueue(); // If this is the final stage, don't allow extra stage if( GAMESTATE->IsFinalStage() ) GAMESTATE->m_bBackedOutOfFinalStage = true; // Disallow backing out of extra stage if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() ) SCREENMAN->PostMessageToTopScreen( SM_BeginFailed, 0 ); else m_Cancel.StartTransitioning( SM_GoToScreenAfterBack ); } void ScreenGameplay::AbortGiveUp( bool bShowText ) { if( m_GiveUpTimer.IsZero() ) return; m_textDebug.StopTweening(); if( bShowText ) m_textDebug.SetText( GIVE_UP_ABORTED_TEXT ); // otherwise tween out the text that's there m_textDebug.BeginTweening( 1/2.f ); m_textDebug.SetDiffuse( RageColor(1,1,1,0) ); m_GiveUpTimer.SetZero(); } void ScreenGameplay::Input( const InputEventPlus &input ) { //LOG->Trace( "ScreenGameplay::Input()" ); if( input.type == IET_LEVEL_CHANGED ) return; if( m_bPaused ) { /* If we're paused, only accept MENU_BUTTON_START to unpause. */ if( input.MenuI.IsValid() && GAMESTATE->IsHumanPlayer(input.MenuI.player) && input.MenuI.button == MENU_BUTTON_START && input.type == IET_FIRST_PRESS ) { if( m_PauseController == GAME_CONTROLLER_INVALID || m_PauseController == input.GameI.controller ) this->PauseGame( false ); } return; } if( input.MenuI.IsValid() && m_DancingState != STATE_OUTRO && GAMESTATE->IsHumanPlayer(input.MenuI.player) && (!GAMESTATE->m_bMultiplayer || input.mp == MultiPlayer_1) && !m_Cancel.IsTransitioning() ) { /* Allow bailing out by holding the START button of all active players. This * gives a way to "give up" when a back button isn't available. Doing this is * treated as failing the song, unlike BACK, since it's always available. * * However, if this is also a style button, don't do this. (pump center = start) */ bool bHoldingGiveUp = false; bHoldingGiveUp |= ( START_GIVES_UP && input.MenuI.button == MENU_BUTTON_START && !input.StyleI.IsValid() ); bHoldingGiveUp |= ( BACK_GIVES_UP && input.MenuI.button == MENU_BUTTON_BACK && !input.StyleI.IsValid() ); if( bHoldingGiveUp ) { /* No PREFSMAN->m_bDelayedEscape; always delayed. */ if( input.type==IET_RELEASE ) AbortGiveUp( true ); else if( input.type==IET_FIRST_PRESS && m_GiveUpTimer.IsZero() ) { m_textDebug.SetText( GIVE_UP_TEXT ); m_textDebug.StopTweening(); m_textDebug.SetDiffuse( RageColor(1,1,1,0) ); m_textDebug.BeginTweening( 1/8.f ); m_textDebug.SetDiffuse( RageColor(1,1,1,1) ); m_GiveUpTimer.Touch(); /* start the timer */ } return; } /* Only handle MENU_BUTTON_BACK as a regular BACK button if BACK_GIVES_UP is * disabled. */ if( input.MenuI.button == MENU_BUTTON_BACK && !BACK_GIVES_UP ) { if( ((!PREFSMAN->m_bDelayedBack && input.type==IET_FIRST_PRESS) || (input.DeviceI.device==DEVICE_KEYBOARD && (input.type==IET_SLOW_REPEAT||input.type==IET_FAST_REPEAT)) || (input.DeviceI.device!=DEVICE_KEYBOARD && input.type==IET_FAST_REPEAT)) ) { LOG->Trace("Player %i went back", input.MenuI.player+1); BeginBackingOutFromGameplay(); } else if( PREFSMAN->m_bDelayedBack && input.type==IET_FIRST_PRESS ) { m_textDebug.SetText( "Continue holding BACK to quit" ); m_textDebug.StopTweening(); m_textDebug.SetDiffuse( RageColor(1,1,1,0) ); m_textDebug.BeginTweening( 1/8.f ); m_textDebug.SetDiffuse( RageColor(1,1,1,1) ); } else if( PREFSMAN->m_bDelayedBack && input.type==IET_RELEASE ) { m_textDebug.StopTweening(); m_textDebug.BeginTweening( 1/8.f ); m_textDebug.SetDiffuse( RageColor(1,1,1,0) ); } return; } } /* Nothing below cares about releases. */ if( input.type == IET_RELEASE ) return; if( GAMESTATE->m_bMultiplayer ) { if( input.mp != MultiPlayer_INVALID && input.StyleI.IsValid() && GAMESTATE->IsMultiPlayerEnabled(input.mp) ) { m_vPlayerInfo[input.mp].m_pPlayer->Step( input.StyleI.col, input.DeviceI.ts ); } } else { // // handle a step or battle item activate // if( input.type==IET_FIRST_PRESS && input.StyleI.IsValid() && GAMESTATE->IsHumanPlayer( input.StyleI.player ) ) { AbortGiveUp( true ); if( PREFSMAN->m_AutoPlay == PC_HUMAN ) m_vPlayerInfo[input.StyleI.player].m_pPlayer->Step( input.StyleI.col, input.DeviceI.ts ); } } } /* * Saving StageStats that are affected by the note pattern is a little tricky: * * Stats are cumulative for course play. * * For regular songs, it doesn't matter how we do it; the pattern doesn't change * during play. * * The pattern changes during play in battle and course mode. We want to include these * changes, so run stats for a song after the song finishes. * * If we fail, be sure to include the current song in stats, with the current modifier set. * * So: * * 1. At the end of a song in any mode, pass or fail, add stats for that song (from m_pPlayer). * 2. At the end of gameplay in course mode, add stats for any songs that weren't played, * applying the modifiers the song would have been played with. This doesn't include songs * that were played but failed; that was done in #1. */ void ScreenGameplay::SongFinished() { // save any statistics FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { /* Note that adding stats is only meaningful for the counters (eg. RadarCategory_Jumps), * not for the percentages (RadarCategory_Air). */ RadarValues v; NoteDataUtil::CalculateRadarValues( pi->m_pPlayer->m_NoteData, GAMESTATE->m_pCurSong->m_fMusicLengthSeconds, v ); pi->GetPlayerStageStats()->radarPossible += v; NoteDataWithScoring::GetActualRadarValues( pi->m_pPlayer->m_NoteData, pi->m_pn, GAMESTATE->m_pCurSong->m_fMusicLengthSeconds, v ); pi->GetPlayerStageStats()->radarActual += v; } /* Extremely important: if we don't remove attacks before moving on to the next * screen, they'll still be turned on eventually. */ GAMESTATE->RemoveAllActiveAttacks(); FOREACH_VisiblePlayerInfo( m_vPlayerInfo, pi ) pi->m_pActiveAttackList->Refresh(); } void ScreenGameplay::StageFinished( bool bBackedOut ) { if( GAMESTATE->IsCourseMode() && GAMESTATE->m_PlayMode != PLAY_MODE_ENDLESS ) { LOG->Trace("Stage finished at index %i/%i", GAMESTATE->GetCourseSongIndex(), (int) m_apSongsQueue.size() ); /* +1 to skip the current song; that song has already passed. */ for( unsigned i = GAMESTATE->GetCourseSongIndex()+1; i < m_apSongsQueue.size(); ++i ) { LOG->Trace("Running stats for %i", i ); SetupSong( i ); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) pi->m_pPlayer->ApplyWaitingTransforms(); SongFinished(); } } // save current stage stats if( !bBackedOut ) STATSMAN->m_vPlayedStageStats.push_back( STATSMAN->m_CurStageStats ); /* Reset options. */ GAMESTATE->RestoreStageOptions(); } void ScreenGameplay::HandleScreenMessage( const ScreenMessage SM ) { CHECKPOINT_M( ssprintf("HandleScreenMessage(%i)", SM) ); if( SM == SM_DoneFadingIn ) { SOUND->PlayOnceFromAnnouncer( "gameplay ready" ); m_Ready.StartTransitioning( SM_PlayGo ); } else if( SM == SM_PlayGo ) { if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() ) SOUND->PlayOnceFromAnnouncer( "gameplay here we go extra" ); else if( GAMESTATE->IsFinalStage() ) SOUND->PlayOnceFromAnnouncer( "gameplay here we go final" ); else SOUND->PlayOnceFromAnnouncer( "gameplay here we go normal" ); m_Go.StartTransitioning( SM_None ); GAMESTATE->m_bGameplayLeadIn.Set( false ); m_DancingState = STATE_DANCING; // STATE CHANGE! Now the user is allowed to press Back } else if( SM == SM_NotesEnded ) // received while STATE_DANCING { AbortGiveUp( false ); // don't allow giveup while the next song is loading /* Do this in LoadNextSong, so we don't tween off old attacks until * m_NextSong finishes. */ // GAMESTATE->RemoveAllActiveAttacks(); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { /* If either player's passmark is enabled, check it. */ if( pi->GetPlayerState()->m_PlayerOptions.m_fPassmark > 0 && pi->m_pLifeMeter && pi->m_pLifeMeter->GetLife() < pi->GetPlayerState()->m_PlayerOptions.m_fPassmark ) { LOG->Trace("Player %i failed: life %f is under %f", pi->GetPlayerStateAndStageStatsIndex()+1, pi->m_pLifeMeter->GetLife(), pi->GetPlayerState()->m_PlayerOptions.m_fPassmark ); pi->GetPlayerStageStats()->bFailed = true; } /* Mark failure. This hasn't been done yet if m_bTwoPlayerRecovery is set. */ if( GAMESTATE->GetPlayerFailType(pi->GetPlayerState()) != SongOptions::FAIL_OFF && (pi->m_pLifeMeter && pi->m_pLifeMeter->IsFailing()) || (m_pCombinedLifeMeter && m_pCombinedLifeMeter->IsFailing(pi->GetStepsAndTrailIndex())) ) pi->GetPlayerStageStats()->bFailed = true; if( !pi->GetPlayerStageStats()->bFailed ) pi->GetPlayerStageStats()->iSongsPassed++; // set a life record at the point of failue if( pi->GetPlayerStageStats()->bFailed ) pi->GetPlayerStageStats()->SetLifeRecordAt( 0, STATSMAN->m_CurStageStats.fGameplaySeconds ); } /* If all players have *really* failed (bFailed, not the life meter or * bFailedEarlier): */ const bool bAllReallyFailed = STATSMAN->m_CurStageStats.AllFailed(); const bool bStopCourseEarly = STOP_COURSE_EARLY; const bool bIsLastSong = IsLastSong(); LOG->Trace( "bAllReallyFailed = %d, bStopCourseEarly = %d, bIsLastSong = %d", bAllReallyFailed, bStopCourseEarly, bIsLastSong ); if( bStopCourseEarly || bAllReallyFailed || bIsLastSong ) { // Time to leave from ScreenGameplay HandleScreenMessage( SM_LeaveGameplay ); } else { /* Load the next song in the course. First, fade out and stop the music. */ float fFadeLengthSeconds = MUSIC_FADE_OUT_SECONDS; RageSoundParams p = m_pSoundMusic->GetParams(); p.m_FadeLength = fFadeLengthSeconds; p.m_LengthSeconds = GAMESTATE->m_fMusicSeconds + fFadeLengthSeconds; m_pSoundMusic->SetParams(p); SCREENMAN->PostMessageToTopScreen( SM_StartLoadingNextSong, fFadeLengthSeconds ); return; } } else if( SM == SM_LeaveGameplay ) { // update dancing characters for win / lose DancingCharacters *pDancers = m_SongBackground.GetDancingCharacters(); if( pDancers ) { FOREACH_EnabledPlayerNumberInfo( m_vPlayerInfo, pi ) { /* XXX: In battle modes, switch( GAMESTATE->GetStageResult(p) ). */ if( pi->GetPlayerStageStats()->bFailed ) pDancers->Change2DAnimState( pi->m_pn, AS2D_FAIL ); // fail anim else if( pi->m_pLifeMeter && pi->m_pLifeMeter->GetLife() == 1.0f ) // full life pDancers->Change2DAnimState( pi->m_pn, AS2D_WINFEVER ); // full life pass anim else pDancers->Change2DAnimState( pi->m_pn, AS2D_WIN ); // pass anim } } /* End round. */ if( m_DancingState == STATE_OUTRO ) // ScreenGameplay already ended return; // ignore m_DancingState = STATE_OUTRO; AbortGiveUp( false ); GAMESTATE->RemoveAllActiveAttacks(); FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { if( pi->m_pActiveAttackList ) pi->m_pActiveAttackList->Refresh(); } LIGHTSMAN->SetLightsMode( LIGHTSMODE_ALL_CLEARED ); bool bAllReallyFailed = STATSMAN->m_CurStageStats.AllFailed(); if( bAllReallyFailed ) { this->PostScreenMessage( SM_BeginFailed, 0 ); return; } TweenOffScreen(); m_Out.StartTransitioning( SM_GoToNextScreen ); // do they deserve an extra stage? if( GAMESTATE->HasEarnedExtraStage() ) SOUND->PlayOnceFromAnnouncer( "gameplay extra" ); else SOUND->PlayOnceFromAnnouncer( "gameplay cleared" ); } else if( SM == SM_StartLoadingNextSong ) { m_pSoundMusic->Stop(); /* Next song. */ // give a little life back between stages FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { if( !pi->GetPlayerStageStats()->bFailed ) { if( pi->m_pLifeMeter ) pi->m_pLifeMeter->OnSongEnded(); if( pi->m_pPrimaryScoreDisplay ) pi->m_pPrimaryScoreDisplay->OnSongEnded(); if( pi->m_pSecondaryScoreDisplay ) pi->m_pSecondaryScoreDisplay->OnSongEnded(); } } if( m_pCombinedLifeMeter ) m_pCombinedLifeMeter->OnSongEnded(); int iPlaySongIndex = GAMESTATE->GetCourseSongIndex()+1; iPlaySongIndex %= m_apSongsQueue.size(); MESSAGEMAN->Broadcast( "BeforeLoadingNextCourseSong" ); m_NextSong.Reset(); m_NextSong.PlayCommand( "Start" ); m_NextSong.StartTransitioning( SM_LoadNextSong ); LoadCourseSongNumber( GAMESTATE->GetCourseSongIndex()+1 ); COMMAND( m_sprCourseSongNumber, "ChangeIn" ); } else if( SM == SM_LoadNextSong ) { SongFinished(); COMMAND( m_sprCourseSongNumber, "ChangeOut" ); LoadNextSong(); m_NextSong.Reset(); m_NextSong.PlayCommand( "Finish" ); m_NextSong.StartTransitioning( SM_None ); /* We're fading in, so don't hit any notes for a few seconds; they'll be * obscured by the fade. */ StartPlayingSong( m_NextSong.GetLengthSeconds()+2, 0 ); } else if( SM == SM_PlayToasty ) { if( PREFSMAN->m_bEasterEggs ) if( !m_Toasty.IsTransitioning() && !m_Toasty.IsFinished() ) // don't play if we've already played it once m_Toasty.StartTransitioning(); } else if( SM >= SM_100Combo && SM <= SM_1000Combo ) { int iCombo = (SM-SM_100Combo+1)*100; PlayAnnouncer( ssprintf("gameplay %d combo",iCombo), 2 ); } else if( SM == SM_ComboStopped ) { PlayAnnouncer( "gameplay combo stopped", 2 ); } else if( SM == SM_ComboContinuing ) { PlayAnnouncer( "gameplay combo overflow", 2 ); } else if( SM >= SM_BattleTrickLevel1 && SM <= SM_BattleTrickLevel3 ) { int iTrickLevel = SM-SM_BattleTrickLevel1+1; PlayAnnouncer( ssprintf("gameplay battle trick level%d",iTrickLevel), 3 ); switch( SM ) { case SM_BattleTrickLevel1: m_soundBattleTrickLevel1.Play(); break; case SM_BattleTrickLevel2: m_soundBattleTrickLevel2.Play(); break; case SM_BattleTrickLevel3: m_soundBattleTrickLevel3.Play(); break; default: ASSERT(0); } } else if( SM >= SM_BattleDamageLevel1 && SM <= SM_BattleDamageLevel3 ) { int iDamageLevel = SM-SM_BattleDamageLevel1+1; PlayAnnouncer( ssprintf("gameplay battle damage level%d",iDamageLevel), 3 ); } else if( SM == SM_GoToScreenAfterBack ) { SongFinished(); StageFinished( true ); GAMESTATE->CancelStage(); HandleScreenMessage( SM_GoToPrevScreen ); } else if( SM == SM_GoToNextScreen ) { SongFinished(); StageFinished( false ); //SaveRecordedResults(); } else if( SM == SM_GainFocus ) { /* We do this ourself. */ SOUND->HandleSongTimer( false ); } else if( SM == SM_LoseFocus ) { /* We might have turned the song timer off. Be sure to turn it back on. */ SOUND->HandleSongTimer( true ); } else if( SM == SM_BeginFailed ) { m_DancingState = STATE_OUTRO; AbortGiveUp( false ); m_pSoundMusic->StopPlaying(); m_soundAssistTick.StopPlaying(); /* Stop any queued assist ticks. */ TweenOffScreen(); m_Failed.StartTransitioning( SM_GoToNextScreen ); // show the survive time if extra stage if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() ) { float fMaxSurviveSeconds = 0; FOREACH_EnabledPlayer(p) fMaxSurviveSeconds = max( fMaxSurviveSeconds, STATSMAN->m_CurStageStats.m_player[p].fAliveSeconds ); m_textSurviveTime.SetText( "TIME: " + SecondsToMMSSMsMs(fMaxSurviveSeconds) ); SET_XY_AND_ON_COMMAND( m_textSurviveTime ); } if( GAMESTATE->IsCourseMode() ) { if( GAMESTATE->GetCourseSongIndex() >= int(m_apSongsQueue.size() / 2) ) SOUND->PlayOnceFromAnnouncer( "gameplay oni failed halfway" ); else SOUND->PlayOnceFromAnnouncer( "gameplay oni failed" ); } else { SOUND->PlayOnceFromAnnouncer( "gameplay failed" ); } } else if( SM == SM_Pause ) { /* Ignore SM_Pause when in demonstration. */ if( GAMESTATE->m_bDemonstrationOrJukebox ) return; if( !m_bPaused ) PauseGame( true ); } ScreenWithMenuElements::HandleScreenMessage( SM ); } void ScreenGameplay::Cancel( ScreenMessage smSendWhenDone ) { m_pSoundMusic->Stop(); ScreenWithMenuElements::Cancel( smSendWhenDone ); } void ScreenGameplay::TweenOnScreen() { ON_COMMAND( m_sprLifeFrame ); ON_COMMAND( m_sprCourseSongNumber ); ON_COMMAND( m_textSongOptions ); ON_COMMAND( m_sprScoreFrame ); ON_COMMAND( m_BPMDisplay ); ON_COMMAND( m_MaxCombo ); ON_COMMAND( m_pPlayerScoreList ); if( m_pCombinedLifeMeter ) ON_COMMAND( *m_pCombinedLifeMeter ); FOREACH( PlayerInfo, m_vPlayerInfo, pi ) { if( pi->m_pLifeMeter ) ON_COMMAND( pi->m_pLifeMeter ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { ON_COMMAND( pi->m_ptextCourseSongNumber ); ON_COMMAND( pi->m_ptextStepsDescription ); if( pi->m_pPrimaryScoreDisplay ) ON_COMMAND( pi->m_pPrimaryScoreDisplay ); if( pi->m_pSecondaryScoreDisplay ) ON_COMMAND( pi->m_pSecondaryScoreDisplay ); ON_COMMAND( pi->m_ptextPlayerOptions ); ON_COMMAND( pi->m_pActiveAttackList ); ON_COMMAND( pi->m_pDifficultyIcon ); ON_COMMAND( pi->m_pDifficultyMeter ); if( pi->m_pPlayer->HasNoteField() ) ON_COMMAND( pi->m_pPlayer ); } if( m_bShowScoreboard ) FOREACH_NSScoreBoardColumn( sc ) ON_COMMAND( m_Scoreboard[sc] ); ScreenWithMenuElements::TweenOnScreen(); } void ScreenGameplay::TweenOffScreen() { ScreenWithMenuElements::TweenOffScreen(); OFF_COMMAND( m_sprLifeFrame ); OFF_COMMAND( m_sprCourseSongNumber ); OFF_COMMAND( m_textSongOptions ); OFF_COMMAND( m_sprScoreFrame ); OFF_COMMAND( m_BPMDisplay ); OFF_COMMAND( m_MaxCombo ); OFF_COMMAND( m_pPlayerScoreList ); if( m_pCombinedLifeMeter ) OFF_COMMAND( *m_pCombinedLifeMeter ); FOREACH( PlayerInfo, m_vPlayerInfo, pi ) { if( pi->m_pLifeMeter ) OFF_COMMAND( pi->m_pLifeMeter ); } FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { OFF_COMMAND( pi->m_ptextCourseSongNumber ); OFF_COMMAND( pi->m_ptextStepsDescription ); if( pi->m_pPrimaryScoreDisplay ) OFF_COMMAND( pi->m_pPrimaryScoreDisplay ); if( pi->m_pSecondaryScoreDisplay ) OFF_COMMAND( pi->m_pSecondaryScoreDisplay ); OFF_COMMAND( pi->m_ptextPlayerOptions ); OFF_COMMAND( pi->m_pActiveAttackList ); OFF_COMMAND( pi->m_pDifficultyIcon ); OFF_COMMAND( pi->m_pDifficultyMeter ); if( pi->m_pPlayer->HasNoteField() ) OFF_COMMAND( pi->m_pPlayer ); } if( m_bShowScoreboard ) FOREACH_NSScoreBoardColumn( sc ) OFF_COMMAND( m_Scoreboard[sc] ); m_textDebug.StopTweening(); m_textDebug.BeginTweening( 1/8.f ); m_textDebug.SetDiffuse( RageColor(1,1,1,0) ); } Song *ScreenGameplay::GetNextCourseSong() const { ASSERT( GAMESTATE->IsCourseMode() ); int iPlaySongIndex = GAMESTATE->GetCourseSongIndex()+1; iPlaySongIndex %= m_apSongsQueue.size(); return m_apSongsQueue[iPlaySongIndex]; } void ScreenGameplay::SaveRecordedResults() { FOREACH_HumanPlayer( pn ) { FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) { XNode *p = pi->m_pPlayer->m_NoteData.CreateNode(); DISP_OPT opt; // // Find a file name for the screenshot // FlushDirCache(); vector files; GetDirListing( "Save/recording*", files, false, false ); sort( files.begin(), files.end() ); /* Files should be of the form "recording######.xxx". */ int iIndex = 0; for( int i = files.size()-1; i >= 0; --i ) { static Regex re( "^recording([0-9]{5})\\....$" ); vector matches; if( !re.Compare( files[i], matches ) ) continue; ASSERT( matches.size() == 1 ); iIndex = atoi( matches[0] )+1; break; } RString sFileName = ssprintf( "recording%05d.xml", iIndex ); p->SaveToFile( "Save/"+sFileName, opt ); SAFE_DELETE( p ); return; } } } // lua start #include "LuaBinding.h" class LunaScreenGameplay: public Luna { public: LunaScreenGameplay() { LUA->Register( Register ); } static int GetNextCourseSong( T* p, lua_State *L ) { p->GetNextCourseSong()->PushSelf(L); return 1; } static void Register( Lua *L ) { ADD_METHOD( GetNextCourseSong ); Luna::Register( L ); } }; LUA_REGISTER_DERIVED_CLASS( ScreenGameplay, ScreenWithMenuElements ) // lua end /* * (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. */