990 lines
27 KiB
C++
990 lines
27 KiB
C++
#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 <set>
|
|
#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<PlayerNumber> 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<m_aGameCommands.size(); c++ )
|
|
{
|
|
GameCommand& mc = m_aGameCommands[c];
|
|
|
|
LuaThreadVariable var( "GameCommand", LuaReference::Create(&mc) );
|
|
|
|
// init icon
|
|
if( SHOW_ICON )
|
|
{
|
|
vector<RString> 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<RString> 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<RString> 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; c<m_aGameCommands.size(); c++ )
|
|
{
|
|
const GameCommand& mc = m_aGameCommands[c];
|
|
if( mc.m_sName == (RString) DEFAULT_CHOICE )
|
|
{
|
|
iDefaultChoice = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FOREACH_PlayerNumber( p )
|
|
{
|
|
m_iChoice[p] = (iDefaultChoice!=-1) ? iDefaultChoice : 0;
|
|
CLAMP( m_iChoice[p], 0, (int)m_aGameCommands.size()-1 );
|
|
m_bChosen[p] = false;
|
|
m_bDoubleChoice[p] = false;
|
|
}
|
|
if( !SHARED_SELECTION )
|
|
{
|
|
FOREACH_ENUM( PlayerNumber, pn )
|
|
{
|
|
if( GAMESTATE->IsHumanPlayer(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<PlayerNumber> vpns;
|
|
GetActiveElementPlayerNumbers( vpns );
|
|
|
|
if( SM == SM_PlayPostSwitchPage )
|
|
{
|
|
int iNewChoice = m_iChoice[ GAMESTATE->m_MasterPlayerNumber ];
|
|
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<PlayerNumber> vpns;
|
|
GetActiveElementPlayerNumbers( vpns );
|
|
|
|
for( unsigned c=0; c<m_aGameCommands.size(); c++ )
|
|
{
|
|
if( SHOW_ICON )
|
|
m_vsprIcon[c]->PlayCommand( 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<int> seen;
|
|
try_again:
|
|
|
|
map<int,int>::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->m_MasterPlayerNumber] );
|
|
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<PlayerNumber> 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<PlayerNumber> 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<AutoActor> &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->m_MasterPlayerNumber != PLAYER_INVALID )
|
|
return GAMESTATE->m_MasterPlayerNumber;
|
|
|
|
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<AutoActor> &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
|
|
// TODO: Add a param to differentiate this from the message below.
|
|
MESSAGEMAN->Broadcast( (MessageID)(Message_MenuStartP1+pn) );
|
|
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<PlayerNumber> vpns;
|
|
GetActiveElementPlayerNumbers( vpns );
|
|
|
|
if( SHOW_ICON )
|
|
{
|
|
for( unsigned c=0; c<m_aGameCommands.size(); c++ )
|
|
{
|
|
m_vsprIcon[c]->PlayCommand( (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; c<m_aGameCommands.size(); c++ )
|
|
{
|
|
m_vsprScroll[*p][c]->PlayCommand( 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<PlayerNumber> vpns;
|
|
GetActiveElementPlayerNumbers( vpns );
|
|
|
|
for( unsigned c=0; c<m_aGameCommands.size(); c++ )
|
|
{
|
|
if( GetPage(c) != GetCurrentPage() )
|
|
continue; // skip
|
|
|
|
bool bSelectedByEitherPlayer = false;
|
|
FOREACH( PlayerNumber, vpns, p )
|
|
{
|
|
if( m_iChoice[*p] == (int)c )
|
|
bSelectedByEitherPlayer = true;
|
|
}
|
|
|
|
if( SHOW_ICON )
|
|
m_vsprIcon[c]->PlayCommand( 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<ScreenSelectMaster>
|
|
{
|
|
public:
|
|
static int GetSelectionIndex( T* p, lua_State *L ) { lua_pushnumber( L, p->GetPlayerSelectionIndex(Enum::Check<PlayerNumber>(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.
|
|
*/
|