#include "global.h" #include "ScreenSelectMaster.h" #include "ScreenManager.h" #include "GameManager.h" #include "ThemeManager.h" #include "GameSoundManager.h" #include "GameState.h" #include "AnnouncerManager.h" #include "GameCommand.h" #include "ActorUtil.h" #include "RageLog.h" #include #include "Foreach.h" #include "InputEventPlus.h" static const char *MenuDirNames[] = { "Up", "Down", "Left", "Right", "Auto", }; XToString( MenuDir ); AutoScreenMessage( SM_PlayPostSwitchPage ); static RString CURSOR_OFFSET_X_FROM_ICON_NAME( size_t p ) { return ssprintf("CursorP%dOffsetXFromIcon",int(p+1)); } static RString CURSOR_OFFSET_Y_FROM_ICON_NAME( size_t p ) { return ssprintf("CursorP%dOffsetYFromIcon",int(p+1)); } // e.g. "OptionOrderLeft=0:1,1:2,2:3,3:4" static RString OPTION_ORDER_NAME( size_t dir ) { return "OptionOrder"+MenuDirToString((MenuDir)dir); } REGISTER_SCREEN_CLASS( ScreenSelectMaster ); #define GetActiveElementPlayerNumbers( vpns ) \ if( SHARED_SELECTION ) { \ vpns.push_back( PLAYER_1 ); \ } else { \ FOREACH_HumanPlayer( p ) \ vpns.push_back( p ); \ } ScreenSelectMaster::ScreenSelectMaster() { ZERO( m_iChoice ); ZERO( m_bChosen ); ZERO( m_bDoubleChoice ); } void ScreenSelectMaster::Init() { SHOW_ICON.Load( m_sName, "ShowIcon" ); SHOW_SCROLLER.Load( m_sName, "ShowScroller" ); SHOW_CURSOR.Load( m_sName, "ShowCursor" ); SHARED_SELECTION.Load( m_sName, "SharedSelection" ); USE_ICON_METRICS.Load( m_sName, "UseIconMetrics" ); NUM_CHOICES_ON_PAGE_1.Load( m_sName, "NumChoicesOnPage1" ); CURSOR_OFFSET_X_FROM_ICON.Load( m_sName, CURSOR_OFFSET_X_FROM_ICON_NAME, NUM_PLAYERS ); CURSOR_OFFSET_Y_FROM_ICON.Load( m_sName, CURSOR_OFFSET_Y_FROM_ICON_NAME, NUM_PLAYERS ); PER_CHOICE_ICON_ELEMENT.Load( m_sName, "PerChoiceIconElement" ); PRE_SWITCH_PAGE_SECONDS.Load( m_sName, "PreSwitchPageSeconds" ); POST_SWITCH_PAGE_SECONDS.Load( m_sName, "PostSwitchPageSeconds" ); OPTION_ORDER.Load( m_sName, OPTION_ORDER_NAME, NUM_MenuDir ); DO_SWITCH_ANYWAYS.Load( m_sName, "DoSwitchAnyways" ); WRAP_CURSOR.Load( m_sName, "WrapCursor" ); WRAP_SCROLLER.Load( m_sName, "WrapScroller" ); LOOP_SCROLLER.Load( m_sName, "LoopScroller" ); PER_CHOICE_SCROLL_ELEMENT.Load( m_sName, "PerChoiceScrollElement" ); ALLOW_REPEATING_INPUT.Load( m_sName, "AllowRepeatingInput" ); SCROLLER_SECONDS_PER_ITEM.Load( m_sName, "ScrollerSecondsPerItem" ); SCROLLER_NUM_ITEMS_TO_DRAW.Load( m_sName, "ScrollerNumItemsToDraw" ); SCROLLER_TRANSFORM.Load( m_sName, "ScrollerTransform" ); //SCROLLER_TWEEN.Load( m_sName, "ScrollerTween" ); SCROLLER_SUBDIVISIONS.Load( m_sName, "ScrollerSubdivisions" ); DEFAULT_CHOICE.Load( m_sName, "DefaultChoice" ); DOUBLE_PRESS_TO_SELECT.Load(m_sName,"DoublePressToSelect"); ScreenSelect::Init(); m_TrackingRepeatingInput = GameButton_Invalid; vector vpns; GetActiveElementPlayerNumbers( vpns ); #define PLAYER_APPEND_NO_SPACE(p) (SHARED_SELECTION ? RString() : ssprintf("P%d",(p)+1)) this->SubscribeToMessage( SM_MenuTimer ); // init cursor if( SHOW_CURSOR ) { FOREACH( PlayerNumber, vpns, p ) { RString sElement = "Cursor" + PLAYER_APPEND_NO_SPACE(*p); m_sprCursor[*p].Load( THEME->GetPathG(m_sName,sElement) ); sElement.Replace( " ", "" ); m_sprCursor[*p]->SetName( sElement ); this->AddChild( m_sprCursor[*p] ); LOAD_ALL_COMMANDS( m_sprCursor[*p] ); } } // Resize vectors depending on how many choices there are m_vsprIcon.resize( m_aGameCommands.size() ); FOREACH( PlayerNumber, vpns, p ) m_vsprScroll[*p].resize( m_aGameCommands.size() ); for( unsigned c=0; c vs; vs.push_back( "Icon" ); if( PER_CHOICE_ICON_ELEMENT ) vs.push_back( "Choice" + mc.m_sName ); RString sElement = join( " ", vs ); m_vsprIcon[c].Load( THEME->GetPathG(m_sName,sElement) ); RString sName = "Icon" "Choice" + mc.m_sName; m_vsprIcon[c]->SetName( sName ); if( USE_ICON_METRICS ) LOAD_ALL_COMMANDS_AND_SET_XY( m_vsprIcon[c] ); this->AddChild( m_vsprIcon[c] ); } // init scroll if( SHOW_SCROLLER ) { FOREACH( PlayerNumber, vpns, p ) { vector vs; vs.push_back( "Scroll" ); if( PER_CHOICE_SCROLL_ELEMENT ) vs.push_back( "Choice" + mc.m_sName ); if( !SHARED_SELECTION ) vs.push_back( PLAYER_APPEND_NO_SPACE(*p) ); RString sElement = join( " ", vs ); m_vsprScroll[*p][c].Load( THEME->GetPathG(m_sName,sElement) ); RString sName = "Scroll" "Choice" + mc.m_sName; if( !SHARED_SELECTION ) sName += PLAYER_APPEND_NO_SPACE(*p); m_vsprScroll[*p][c]->SetName( sName ); m_Scroller[*p].AddChild( m_vsprScroll[*p][c] ); } } } // init scroll if( SHOW_SCROLLER ) { FOREACH( PlayerNumber, vpns, p ) { m_Scroller[*p].SetLoop( LOOP_SCROLLER ); m_Scroller[*p].SetNumItemsToDraw( SCROLLER_NUM_ITEMS_TO_DRAW ); m_Scroller[*p].Load2(); m_Scroller[*p].SetTransformFromReference( SCROLLER_TRANSFORM ); m_Scroller[*p].SetSecondsPerItem( SCROLLER_SECONDS_PER_ITEM ); m_Scroller[*p].SetNumSubdivisions( SCROLLER_SUBDIVISIONS ); m_Scroller[*p].SetName( "Scroller"+PLAYER_APPEND_NO_SPACE(*p) ); LOAD_ALL_COMMANDS_AND_SET_XY( m_Scroller[*p] ); this->AddChild( &m_Scroller[*p] ); } } FOREACH_ENUM( Page, page ) { m_sprMore[page].Load( THEME->GetPathG(m_sName, ssprintf("more page%d",page+1)) ); m_sprMore[page]->SetName( ssprintf("MorePage%d",page+1) ); LOAD_ALL_COMMANDS_AND_SET_XY( m_sprMore[page] ); this->AddChild( m_sprMore[page] ); m_sprExplanation[page].Load( THEME->GetPathG(m_sName, ssprintf("explanation page%d",page+1)) ); m_sprExplanation[page]->SetName( ssprintf("ExplanationPage%d",page+1) ); LOAD_ALL_COMMANDS_AND_SET_XY( m_sprExplanation[page] ); this->AddChild( m_sprExplanation[page] ); } m_soundChange.Load( THEME->GetPathS(m_sName,"change"), true ); m_soundDifficult.Load( ANNOUNCER->GetPathTo("select difficulty challenge") ); m_soundStart.Load( THEME->GetPathS(m_sName,"start") ); // init m_Next order info FOREACH_MenuDir( dir ) { const RString order = OPTION_ORDER.GetValue( dir ); vector parts; split( order, ",", parts, true ); for( unsigned part = 0; part < parts.size(); ++part ) { int from, to; if( sscanf( parts[part], "%d:%d", &from, &to ) != 2 ) { LOG->Warn( "%s::OptionOrder%s parse error", m_sName.c_str(), MenuDirToString(dir).c_str() ); continue; } --from; --to; m_mapCurrentChoiceToNextChoice[dir][from] = to; } if( m_mapCurrentChoiceToNextChoice[dir].empty() ) // Didn't specify any mappings { // Fill with reasonable defaults for( unsigned c = 0; c < m_aGameCommands.size(); ++c ) { int add; switch( dir ) { case MenuDir_Up: case MenuDir_Left: add = -1; break; default: add = +1; break; } m_mapCurrentChoiceToNextChoice[dir][c] = c + add; // Always wrap around MenuDir_Auto. if( dir == MenuDir_Auto || (bool)WRAP_CURSOR ) wrap( m_mapCurrentChoiceToNextChoice[dir][c], m_aGameCommands.size() ); else m_mapCurrentChoiceToNextChoice[dir][c] = clamp( m_mapCurrentChoiceToNextChoice[dir][c], 0, (int)m_aGameCommands.size()-1 ); } } } m_bDoubleChoiceNoSound = false; } RString ScreenSelectMaster::GetDefaultChoice() { return DEFAULT_CHOICE.GetValue(); } void ScreenSelectMaster::BeginScreen() { // TODO: Move default choice to ScreenSelect int iDefaultChoice = -1; for( unsigned c=0; cIsHumanPlayer(pn) ) continue; if( SHOW_CURSOR ) { if(m_sprCursor[pn]) m_sprCursor[pn]->SetVisible( false ); } if( SHOW_SCROLLER ) m_Scroller[pn].SetVisible( false ); } } this->UpdateSelectableChoices(); ScreenSelect::BeginScreen(); // Call GetTweenTimeLeft after the base BeginScreen has started the in Transition. m_fLockInputSecs = this->GetTweenTimeLeft(); } void ScreenSelectMaster::HandleScreenMessage( const ScreenMessage SM ) { ScreenSelect::HandleScreenMessage( SM ); vector vpns; GetActiveElementPlayerNumbers( vpns ); if( SM == SM_PlayPostSwitchPage ) { int iNewChoice = m_iChoice[ GAMESTATE->GetMasterPlayerNumber() ]; Page newPage = GetPage( iNewChoice ); Message msg("PostSwitchPage"); msg.SetParam( "NewPageIndex", (int)newPage ); if( SHOW_CURSOR ) { FOREACH( PlayerNumber, vpns, p ) m_sprCursor[*p]->HandleMessage( msg ); } if( SHOW_SCROLLER ) { FOREACH( PlayerNumber, vpns, p ) { int iChoice = m_iChoice[*p]; m_vsprScroll[*p][iChoice]->HandleMessage( msg ); } } MESSAGEMAN->Broadcast(msg); m_fLockInputSecs = POST_SWITCH_PAGE_SECONDS; } else if( SM == SM_MenuTimer ) { if(DOUBLE_PRESS_TO_SELECT) { FOREACH_HumanPlayer(p) { m_bDoubleChoiceNoSound = true; m_bDoubleChoice[p] = true; InputEventPlus iep; iep.pn = p; MenuStart( iep ); } } } } int ScreenSelectMaster::GetSelectionIndex( PlayerNumber pn ) { return m_iChoice[pn]; } void ScreenSelectMaster::UpdateSelectableChoices() { vector vpns; GetActiveElementPlayerNumbers( vpns ); for( unsigned c=0; cPlayCommand( m_aGameCommands[c].IsPlayable()? "Enabled":"Disabled" ); FOREACH( PlayerNumber, vpns, p ) if( m_vsprScroll[*p][c].IsLoaded() ) m_vsprScroll[*p][c]->PlayCommand( m_aGameCommands[c].IsPlayable()? "Enabled":"Disabled" ); } /* If no options are playable at all, just wait. Some external * stimulus may make options available (such as coin insertion). * If any options are playable, make sure one is selected. */ FOREACH_HumanPlayer( p ) { if( !m_aGameCommands[m_iChoice[p]].IsPlayable() ) Move( p, MenuDir_Auto ); } } bool ScreenSelectMaster::AnyOptionsArePlayable() const { for( unsigned i = 0; i < m_aGameCommands.size(); ++i ) if( m_aGameCommands[i].IsPlayable() ) return true; return false; } bool ScreenSelectMaster::Move( PlayerNumber pn, MenuDir dir ) { if( !AnyOptionsArePlayable() ) return false; int iSwitchToIndex = m_iChoice[pn]; set seen; try_again: map::const_iterator iter = m_mapCurrentChoiceToNextChoice[dir].find( iSwitchToIndex ); if( iter != m_mapCurrentChoiceToNextChoice[dir].end() ) iSwitchToIndex = iter->second; if( iSwitchToIndex < 0 || iSwitchToIndex >= (int) m_aGameCommands.size() ) // out of choice range return false; // can't go that way if( seen.find(iSwitchToIndex) != seen.end() ) return false; // went full circle and none found seen.insert( iSwitchToIndex ); if( !m_aGameCommands[iSwitchToIndex].IsPlayable() && !DO_SWITCH_ANYWAYS ) goto try_again; return ChangeSelection( pn, dir, iSwitchToIndex ); } void ScreenSelectMaster::MenuLeft( const InputEventPlus &input ) { PlayerNumber pn = input.pn; if( m_fLockInputSecs > 0 || m_bChosen[pn] ) return; if( input.type == IET_RELEASE ) return; if( input.type != IET_FIRST_PRESS ) { if( !ALLOW_REPEATING_INPUT ) return; if( m_TrackingRepeatingInput != input.MenuI ) return; } if( Move(pn, MenuDir_Left) ) { m_TrackingRepeatingInput = input.MenuI; m_soundChange.Play(); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuLeftP1+pn) ); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuSelectionChanged) ); // if they use double select if(DOUBLE_PRESS_TO_SELECT) { m_bDoubleChoice[pn] = false; // player has cancelled their selection } } } void ScreenSelectMaster::MenuRight( const InputEventPlus &input ) { PlayerNumber pn = input.pn; if( m_fLockInputSecs > 0 || m_bChosen[pn] ) return; if( input.type == IET_RELEASE ) return; if( input.type != IET_FIRST_PRESS ) { if( !ALLOW_REPEATING_INPUT ) return; if( m_TrackingRepeatingInput != input.MenuI ) return; } if( Move(pn, MenuDir_Right) ) { m_TrackingRepeatingInput = input.MenuI; m_soundChange.Play(); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuRightP1+pn) ); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuSelectionChanged) ); // if they use double select if(DOUBLE_PRESS_TO_SELECT) { m_bDoubleChoice[pn] = false; // player has cancelled their selection } } } void ScreenSelectMaster::MenuUp( const InputEventPlus &input ) { PlayerNumber pn = input.pn; if( m_fLockInputSecs > 0 || m_bChosen[pn] ) return; if( input.type == IET_RELEASE ) return; if( input.type != IET_FIRST_PRESS ) { if( !ALLOW_REPEATING_INPUT ) return; if( m_TrackingRepeatingInput != input.MenuI ) return; } if( Move(pn, MenuDir_Up) ) { m_TrackingRepeatingInput = input.MenuI; m_soundChange.Play(); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuUpP1+pn) ); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuSelectionChanged) ); // if they use double select if(DOUBLE_PRESS_TO_SELECT) { m_bDoubleChoice[pn] = false; // player has cancelled their selection } } } void ScreenSelectMaster::MenuDown( const InputEventPlus &input ) { PlayerNumber pn = input.pn; if( m_fLockInputSecs > 0 || m_bChosen[pn] ) return; if( input.type == IET_RELEASE ) return; if( input.type != IET_FIRST_PRESS ) { if( !ALLOW_REPEATING_INPUT ) return; if( m_TrackingRepeatingInput != input.MenuI ) return; } if( Move(pn, MenuDir_Down) ) { m_TrackingRepeatingInput = input.MenuI; m_soundChange.Play(); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuDownP1+pn) ); MESSAGEMAN->Broadcast( (MessageID)(Message_MenuSelectionChanged) ); // if they use double select if(DOUBLE_PRESS_TO_SELECT) { m_bDoubleChoice[pn] = false; // player has cancelled their selection } } } bool ScreenSelectMaster::ChangePage( int iNewChoice ) { Page oldPage = GetPage( m_iChoice[GAMESTATE->GetMasterPlayerNumber()] ); Page newPage = GetPage( iNewChoice ); // If anyone has already chosen, don't allow changing of pages FOREACH_PlayerNumber( p ) { if( GAMESTATE->IsHumanPlayer(p) && m_bChosen[p] ) return false; } // change both players FOREACH_PlayerNumber( p ) m_iChoice[p] = iNewChoice; const RString sIconAndExplanationCommand = ssprintf( "SwitchToPage%d", newPage+1 ); if( SHOW_ICON ) for( unsigned c = 0; c < m_aGameCommands.size(); ++c ) m_vsprIcon[c]->PlayCommand( sIconAndExplanationCommand ); FOREACH_ENUM( Page, page ) { m_sprExplanation[page]->PlayCommand( sIconAndExplanationCommand ); m_sprMore[page]->PlayCommand( sIconAndExplanationCommand ); } vector vpns; GetActiveElementPlayerNumbers( vpns ); Message msg("PreSwitchPage"); msg.SetParam( "OldPageIndex", (int)oldPage ); msg.SetParam( "NewPageIndex", (int)newPage ); MESSAGEMAN->Broadcast( msg ); FOREACH( PlayerNumber, vpns, p ) { if( GAMESTATE->IsHumanPlayer(*p) ) { if( SHOW_CURSOR ) { m_sprCursor[*p]->HandleMessage( msg ); m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) ); } if( SHOW_SCROLLER ) m_vsprScroll[*p][m_iChoice[*p]]->HandleMessage( msg ); } } if( newPage == PAGE_2 ) { // XXX: only play this once (I thought we already did that?) // Play it on every change to page 2. -Chris // That sounds ugly if you go back and forth quickly. -g // Should we lock input while it's scrolling? -Chris m_soundDifficult.Stop(); m_soundDifficult.PlayRandom(); } m_fLockInputSecs = PRE_SWITCH_PAGE_SECONDS; this->PostScreenMessage( SM_PlayPostSwitchPage, GetTweenTimeLeft() ); return true; } bool ScreenSelectMaster::ChangeSelection( PlayerNumber pn, MenuDir dir, int iNewChoice ) { if( iNewChoice == m_iChoice[pn] ) return false; // already there Page page = GetPage( iNewChoice ); if( GetPage(m_iChoice[pn]) != page ) { return ChangePage( iNewChoice ); } vector vpns; if( SHARED_SELECTION || page != PAGE_1 ) { /* Set the new m_iChoice even for disabled players, since a player might * join on a SHARED_SELECTION after the cursor has been moved. */ FOREACH_ENUM( PlayerNumber, p ) vpns.push_back( p ); } else { vpns.push_back( pn ); } FOREACH( PlayerNumber, vpns, p ) { const int iOldChoice = m_iChoice[*p]; m_iChoice[*p] = iNewChoice; if( SHOW_ICON ) { /* XXX: If !SharedPreviewAndCursor, this is incorrect. (Nothing uses * both icon focus and !SharedPreviewAndCursor right now.) * What is SharedPreviewAndCursor? -- Steve */ bool bOldStillHasFocus = false; bool bNewAlreadyHadFocus = false; FOREACH_HumanPlayer( p2 ) { if( p2 == *p ) continue; bOldStillHasFocus |= m_iChoice[p2] == iOldChoice; bNewAlreadyHadFocus |= m_iChoice[p2] == iNewChoice; } if(DOUBLE_PRESS_TO_SELECT) { // this player is currently on a single press, which they are cancelling if(m_bDoubleChoice[pn]) { if( !bOldStillHasFocus ) m_vsprIcon[iOldChoice]->PlayCommand( "LostSelectedLoseFocus" ); if( !bNewAlreadyHadFocus ) m_vsprIcon[iNewChoice]->PlayCommand( "LostSelectedGainFocus" ); } else { if( !bOldStillHasFocus ) m_vsprIcon[iOldChoice]->PlayCommand( "LoseFocus" ); if( !bNewAlreadyHadFocus ) m_vsprIcon[iNewChoice]->PlayCommand( "GainFocus" ); } } else // not using double selection { if( !bOldStillHasFocus ) m_vsprIcon[iOldChoice]->PlayCommand( "LoseFocus" ); if( !bNewAlreadyHadFocus ) m_vsprIcon[iNewChoice]->PlayCommand( "GainFocus" ); } } if( SHOW_CURSOR ) { if( GAMESTATE->IsHumanPlayer(*p) ) { m_sprCursor[*p]->PlayCommand( "Change" ); m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) ); } } if( SHOW_SCROLLER ) { ActorScroller &scroller = (SHARED_SELECTION || page != PAGE_1 ? m_Scroller[0] : m_Scroller[*p]); vector &vScroll = (SHARED_SELECTION || page != PAGE_1 ? m_vsprScroll[0] : m_vsprScroll[*p]); if( WRAP_SCROLLER ) { // HACK: We can't tell from the option orders whether or not we wrapped. // For now, assume that the order is increasing left to right. int iPressedDir = (dir == MenuDir_Left || dir == MenuDir_Up) ? -1 : +1; int iActualDir = (iOldChoice < iNewChoice) ? +1 : -1; if( iPressedDir != iActualDir ) // wrapped { float fItem = scroller.GetCurrentItem(); int iNumChoices = m_aGameCommands.size(); fItem += iActualDir * iNumChoices; scroller.SetCurrentAndDestinationItem( fItem ); } } scroller.SetDestinationItem( (float)iNewChoice ); // using double selections if(DOUBLE_PRESS_TO_SELECT) { // this player is currently on a single press, which they are cancelling if(m_bDoubleChoice[pn]) { vScroll[iOldChoice]->PlayCommand( "LostSelectedLoseFocus" ); vScroll[iNewChoice]->PlayCommand( "LostSelectedGainFocus" ); } else // the player hasn't made any selections yet { vScroll[iOldChoice]->PlayCommand( "LoseFocus" ); vScroll[iNewChoice]->PlayCommand( "GainFocus" ); } } else // regular lose/gain focus { vScroll[iOldChoice]->PlayCommand( "LoseFocus" ); vScroll[iNewChoice]->PlayCommand( "GainFocus" ); } } } return true; } PlayerNumber ScreenSelectMaster::GetSharedPlayer() { if( GAMESTATE->GetMasterPlayerNumber() != PLAYER_INVALID ) return GAMESTATE->GetMasterPlayerNumber(); return PLAYER_1; } ScreenSelectMaster::Page ScreenSelectMaster::GetPage( int iChoiceIndex ) const { return iChoiceIndex < NUM_CHOICES_ON_PAGE_1? PAGE_1:PAGE_2; } ScreenSelectMaster::Page ScreenSelectMaster::GetCurrentPage() const { // Both players are guaranteed to be on the same page. return GetPage( m_iChoice[GetSharedPlayer()] ); } float ScreenSelectMaster::DoMenuStart( PlayerNumber pn ) { if( m_bChosen[pn] ) return 0; bool bAnyChosen = false; FOREACH_PlayerNumber( p ) bAnyChosen |= m_bChosen[p]; m_bChosen[pn] = true; this->PlayCommand( "MadeChoice"+PlayerNumberToString(pn) ); bool bIsFirstToChoose = bAnyChosen; float fSecs = 0; if( bIsFirstToChoose ) { FOREACH_ENUM( Page, page ) { m_sprMore[page]->PlayCommand( "Off" ); fSecs = max( fSecs, m_sprMore[page]->GetTweenTimeLeft() ); } } if( SHOW_CURSOR ) { if(m_sprCursor[pn] != NULL) { m_sprCursor[pn]->PlayCommand( "Choose" ); fSecs = max( fSecs, m_sprCursor[pn]->GetTweenTimeLeft() ); } } return fSecs; } void ScreenSelectMaster::MenuStart( const InputEventPlus &input ) { if( input.type != IET_FIRST_PRESS ) return; PlayerNumber pn = input.pn; if( m_fLockInputSecs > 0 ) return; if( m_bChosen[pn] ) return; if( !ProcessMenuStart( pn ) ) return; // double press is enabled and the player hasn't made their first press if(DOUBLE_PRESS_TO_SELECT && !m_bDoubleChoice[pn]) { m_soundStart.PlayCopy(); m_bDoubleChoice[pn] = true; if(SHOW_SCROLLER) { vector &vScroll = SHARED_SELECTION ? m_vsprScroll[0] : m_vsprScroll[pn]; vScroll[m_iChoice[pn]]->PlayCommand( "InitialSelection" ); } return; } const GameCommand &mc = m_aGameCommands[m_iChoice[pn]]; /* If no options are playable, then we're just waiting for one to become available. * If any options are playable, then the selection must be playable. */ if( !AnyOptionsArePlayable() ) return; SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo(ssprintf("%s comment %s",m_sName.c_str(), mc.m_sName.c_str())) ); // Play a copy of the sound, so it'll finish playing even if we leave the screen immediately. if( mc.m_sSoundPath.empty() && !m_bDoubleChoiceNoSound ) m_soundStart.PlayCopy(); if( mc.m_sScreen.empty() ) { mc.ApplyToAllPlayers(); // We want to be able to broadcast a Start message to the theme, in // case a themer wants to handle something. -aj Message msg( MessageIDToString((MessageID)(Message_MenuStartP1+pn)) ); msg.SetParam( "ScreenEmpty", true ); MESSAGEMAN->Broadcast( msg ); return; } float fSecs = 0; bool bAllDone = true; if( (bool)SHARED_SELECTION || GetCurrentPage() == PAGE_2 ) { // Only one player has to pick. Choose this for all the other players, too. FOREACH_PlayerNumber( p ) { ASSERT( !m_bChosen[p] ); fSecs = max( fSecs, DoMenuStart(p) ); // no harm in calling this for an unjoined player } } else { fSecs = max( fSecs, DoMenuStart(pn) ); // check to see if everyone has chosen FOREACH_HumanPlayer( p ) bAllDone &= m_bChosen[p]; } if( bAllDone ) { // broadcast MenuStart just like MenuLeft/Right/etc. MESSAGEMAN->Broadcast( (MessageID)(Message_MenuStartP1+pn) ); this->PostScreenMessage( SM_BeginFadingOut, fSecs );// tell our owner it's time to move on } } /* We want all items to always run OnCommand and either GainFocus or LoseFocus * on tween-in. If we only run OnCommand, then it has to contain a copy of * either GainFocus or LoseFocus, which implies that the default setting is * hard-coded in the theme. Always run both. * However, the actual tween-in is OnCommand; we don't always want to actually * run through the Gain/LoseFocus tweens during initial tween-in. So, we run * the focus command first, do a FinishTweening to pop it in place, and then * run OnCommand. This means that the focus command should be position neutral; * eg. only use "addx", not "x". */ void ScreenSelectMaster::TweenOnScreen() { vector vpns; GetActiveElementPlayerNumbers( vpns ); if( SHOW_ICON ) { for( unsigned c=0; cPlayCommand( (int(c) == m_iChoice[0])? "GainFocus":"LoseFocus" ); m_vsprIcon[c]->FinishTweening(); } } if( SHOW_SCROLLER ) { FOREACH( PlayerNumber, vpns, p ) { // Play Gain/LoseFocus before playing the on command. // Gain/Lose will often stop tweening, which ruins the OnCommand. for( unsigned c=0; cPlayCommand( int(c) == m_iChoice[*p]? "GainFocus":"LoseFocus" ); m_vsprScroll[*p][c]->FinishTweening(); } m_Scroller[*p].SetCurrentAndDestinationItem( (float)m_iChoice[*p] ); } } // Need to SetXY of Cursor after Icons since it depends on the Icons' positions. if( SHOW_CURSOR ) { FOREACH( PlayerNumber, vpns, p ) { if( GAMESTATE->IsHumanPlayer(*p) ) m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) ); } } ScreenSelect::TweenOnScreen(); } void ScreenSelectMaster::TweenOffScreen() { ScreenSelect::TweenOffScreen(); vector vpns; GetActiveElementPlayerNumbers( vpns ); for( unsigned c=0; cPlayCommand( bSelectedByEitherPlayer? "OffFocused":"OffUnfocused" ); if( SHOW_SCROLLER ) { FOREACH( PlayerNumber, vpns, p ) m_vsprScroll[*p][c]->PlayCommand( bSelectedByEitherPlayer? "OffFocused":"OffUnfocused" ); } } } // Use DestX and DestY so that the cursor can move to where the icon will be rather than where it is. float ScreenSelectMaster::GetCursorX( PlayerNumber pn ) { int iChoice = m_iChoice[pn]; AutoActor &spr = m_vsprIcon[iChoice]; return spr->GetDestX() + CURSOR_OFFSET_X_FROM_ICON.GetValue(pn); } float ScreenSelectMaster::GetCursorY( PlayerNumber pn ) { int iChoice = m_iChoice[pn]; AutoActor &spr = m_vsprIcon[iChoice]; return spr->GetDestY() + CURSOR_OFFSET_Y_FROM_ICON.GetValue(pn); } // lua start #include "LuaBinding.h" /** @brief Allow Lua to have access to the ScreenSelectMaster. */ class LunaScreenSelectMaster: public Luna { public: static int GetSelectionIndex( T* p, lua_State *L ) { lua_pushnumber( L, p->GetPlayerSelectionIndex(Enum::Check(L, 1)) ); return 1; } // should I even bother adding this? -aj // would have to make a public function to get this in ssmaster.h: // m_aGameCommands[i].m_sName // static int SelectionIndexToChoiceName( T* p, lua_State *L ){ return 1; } LunaScreenSelectMaster() { ADD_METHOD( GetSelectionIndex ); } }; LUA_REGISTER_DERIVED_CLASS( ScreenSelectMaster, ScreenWithMenuElements ) // lua end /* * (c) 2003-2004 Chris Danford * 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. */