Files
itgmania212121/src/ScreenSelectMaster.cpp
T

1321 lines
38 KiB
C++
Raw Normal View History

2010-01-26 21:00:30 -06:00
#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)); }
2010-03-01 00:44:39 -06:00
// e.g. "OptionOrderLeft=0:1,1:2,2:3,3:4"
2010-01-26 21:00:30 -06:00
static RString OPTION_ORDER_NAME( size_t dir ) { return "OptionOrder"+MenuDirToString((MenuDir)dir); }
static RString OPTION_ORDER_NAME_LIST2( size_t dir ) { return "OptionOrderB"+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 );
OPTION_ORDER2.Load( m_sName, OPTION_ORDER_NAME_LIST2, NUM_MenuDir );
DO_SWITCH_ANYWAYS.Load( m_sName, "DoSwitchAnyways" );
2010-01-26 21:00:30 -06:00
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_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] );
}
}
}
// using two lists, load the stuff for the 2nd one
if(m_bUsingTwoLists)
{
// Resize vectors depending on how many choices there are
m_vsprIconB.resize( m_aGameCommandsB.size() );
FOREACH( PlayerNumber, vpns, p )
m_vsprScrollB[*p].resize( m_aGameCommandsB.size() );
for( unsigned c=0; c<m_aGameCommandsB.size(); c++ )
{
GameCommand& mc = m_aGameCommandsB[c];
LuaThreadVariable var( "GameCommand", LuaReference::Create(&mc) );
// init icon
if( SHOW_ICON )
{
vector<RString> vs;
vs.push_back( "List2 Icon" );
if( PER_CHOICE_ICON_ELEMENT )
vs.push_back( "List2 Choice" + mc.m_sName );
RString sElement = join( " ", vs );
m_vsprIconB[c].Load( THEME->GetPathG(m_sName,sElement) );
RString sName = "List2 Icon" "List2 Choice" + mc.m_sName;
m_vsprIconB[c]->SetName( sName );
if( USE_ICON_METRICS )
LOAD_ALL_COMMANDS_AND_SET_XY( m_vsprIconB[c] );
this->AddChild( m_vsprIconB[c] );
}
// init scroll
if( SHOW_SCROLLER )
{
FOREACH( PlayerNumber, vpns, p )
{
vector<RString> vs;
vs.push_back( "List2 Scroll" );
if( PER_CHOICE_SCROLL_ELEMENT )
vs.push_back( "List2 Choice" + mc.m_sName );
if( !SHARED_SELECTION )
vs.push_back( PLAYER_APPEND_NO_SPACE(*p) );
RString sElement = join( " ", vs );
m_vsprScrollB[*p][c].Load( THEME->GetPathG(m_sName,sElement) );
RString sName = "List2 Scroll" "List2 Choice" + mc.m_sName;
if( !SHARED_SELECTION )
sName += PLAYER_APPEND_NO_SPACE(*p);
m_vsprScrollB[*p][c]->SetName( sName );
m_ScrollerB[*p].AddChild( m_vsprScrollB[*p][c] );
}
}
}
// init scroll
if( SHOW_SCROLLER )
{
FOREACH( PlayerNumber, vpns, p )
{
m_ScrollerB[*p].SetLoop( LOOP_SCROLLER );
m_ScrollerB[*p].SetNumItemsToDraw( SCROLLER_NUM_ITEMS_TO_DRAW );
m_ScrollerB[*p].Load2();
m_ScrollerB[*p].SetTransformFromReference( SCROLLER_TRANSFORM );
m_ScrollerB[*p].SetSecondsPerItem( SCROLLER_SECONDS_PER_ITEM );
m_ScrollerB[*p].SetNumSubdivisions( SCROLLER_SUBDIVISIONS );
m_ScrollerB[*p].SetName( "Scroller"+PLAYER_APPEND_NO_SPACE(*p) );
LOAD_ALL_COMMANDS_AND_SET_XY( m_ScrollerB[*p] );
this->AddChild( &m_ScrollerB[*p] );
}
}
FOREACH_MenuDir( dir )
{
const RString order = OPTION_ORDER2.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::OptionOrderB%s parse error", m_sName.c_str(), MenuDirToString(dir).c_str() );
continue;
}
--from;
--to;
m_mapCurrentChoiceToNextChoiceB[dir][from] = to;
}
if( m_mapCurrentChoiceToNextChoiceB[dir].empty() ) // Didn't specify any mappings
{
// Fill with reasonable defaults
for( unsigned c = 0; c < m_aGameCommandsB.size(); ++c )
{
int add;
switch( dir )
{
case MenuDir_Up:
case MenuDir_Left:
add = -1;
break;
default:
add = +1;
break;
}
m_mapCurrentChoiceToNextChoiceB[dir][c] = c + add;
// Always wrap around MenuDir_Auto.
2010-01-26 21:00:30 -06:00
if( dir == MenuDir_Auto || (bool)WRAP_CURSOR )
wrap( m_mapCurrentChoiceToNextChoiceB[dir][c], m_aGameCommandsB.size() );
else
m_mapCurrentChoiceToNextChoiceB[dir][c] = clamp( m_mapCurrentChoiceToNextChoiceB[dir][c], 0, (int)m_aGameCommandsB.size()-1 );
}
}
}
} // end of list2 init
// 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 );
}
2010-01-26 21:00:30 -06:00
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 );
}
}
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 );
}
2010-01-26 21:00:30 -06:00
}
}
}
int ScreenSelectMaster::GetSelectionIndex( PlayerNumber pn )
{
return m_iChoice[pn];
}
void ScreenSelectMaster::UpdateSelectableChoices()
{
vector<PlayerNumber> vpns;
GetActiveElementPlayerNumbers( vpns );
2010-02-02 16:00:10 -06:00
// using two lists and on the 2nd list
if(m_bUsingTwoLists && m_iSelectedList == 1)
2010-01-26 21:00:30 -06:00
{
for( unsigned c=0; c<m_aGameCommands.size(); c++ )
{
if( SHOW_ICON )
{
m_vsprIcon[c]->PlayCommand( "List2Enabled" );
}
2010-02-02 16:00:10 -06:00
2010-01-26 21:00:30 -06:00
FOREACH( PlayerNumber, vpns, p )
if( m_vsprScroll[*p][c].IsLoaded() )
{
m_vsprScroll[*p][c]->PlayCommand( "List2Enabled" );
}
2010-02-02 16:00:10 -06:00
}
2010-01-26 21:00:30 -06:00
for( unsigned c=0; c<m_aGameCommandsB.size(); c++ )
{
if( SHOW_ICON )
{
m_vsprIconB[c]->PlayCommand( "List2Enabled" );
m_vsprIconB[c]->PlayCommand( m_aGameCommandsB[c].IsPlayable()? "Enabled":"Disabled" );
2010-02-02 16:00:10 -06:00
2010-01-26 21:00:30 -06:00
// we will make element 0 the new selection, so it will gain focus
if(c==0)
m_vsprIconB[c]->PlayCommand( "GainFocus" );
else // everything else loses focus
m_vsprIconB[c]->PlayCommand( "LoseFocus" );
}
2010-02-02 16:00:10 -06:00
2010-01-26 21:00:30 -06:00
FOREACH( PlayerNumber, vpns, p )
if( m_vsprScrollB[*p][c].IsLoaded() )
{
m_vsprScrollB[*p][c]->PlayCommand( "List2Enabled" );
m_vsprScrollB[*p][c]->PlayCommand( m_aGameCommandsB[c].IsPlayable()? "Enabled":"Disabled" );
if(c==0)
m_vsprScrollB[*p][c]->PlayCommand( "GainFocus" );
else // everything else loses focus
m_vsprScrollB[*p][c]->PlayCommand( "LoseFocus" );
}
2010-02-02 16:00:10 -06:00
}
2010-01-26 21:00:30 -06:00
}
else
{
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
2010-01-26 21:00:30 -06:00
* stimulus may make options available (such as coin insertion).
* If any options are playable, make sure one is selected. */
2010-01-26 21:00:30 -06:00
FOREACH_HumanPlayer( p )
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
// reset their choice position since we are moving to the new list:
m_iChoice[p] = 0;
2010-01-26 21:00:30 -06:00
if( !m_aGameCommandsB[m_iChoice[p]].IsPlayable() )
Move( p, MenuDir_Auto );
}
else
{
if( !m_aGameCommands[m_iChoice[p]].IsPlayable() )
Move( p, MenuDir_Auto );
}
}
}
bool ScreenSelectMaster::AnyOptionsArePlayable() const
{
// if they're using the second list
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
for( unsigned i = 0; i < m_aGameCommandsB.size(); ++i )
if( m_aGameCommandsB[i].IsPlayable() )
return true;
}
else
{
for( unsigned i = 0; i < m_aGameCommands.size(); ++i )
if( m_aGameCommands[i].IsPlayable() )
return true;
}
2010-01-26 21:00:30 -06:00
return false;
}
bool ScreenSelectMaster::Move( PlayerNumber pn, MenuDir dir )
{
if( !AnyOptionsArePlayable() )
return false;
int iSwitchToIndex = m_iChoice[pn];
set<int> seen;
try_again:
// moving list 2
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
map<int,int>::const_iterator iter = m_mapCurrentChoiceToNextChoiceB[dir].find( iSwitchToIndex );
if( iter != m_mapCurrentChoiceToNextChoiceB[dir].end() )
iSwitchToIndex = iter->second;
if( iSwitchToIndex < 0 || iSwitchToIndex >= (int) m_aGameCommandsB.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_aGameCommandsB[iSwitchToIndex].IsPlayable() )
goto try_again;
}
else
{
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) );
2010-01-26 21:00:30 -06:00
// 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 );
2010-01-26 21:00:30 -06:00
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 );
FOREACH( PlayerNumber, vpns, p )
{
if( SHOW_CURSOR )
{
m_sprCursor[*p]->HandleMessage( msg );
m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) );
}
2010-01-26 21:00:30 -06:00
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
2010-01-26 21:00:30 -06:00
// 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
2010-01-26 21:00:30 -06:00
* 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)
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
// this player is currently on a single press, which they are cancelling
if(m_bDoubleChoice[pn])
{
if( !bOldStillHasFocus )
m_vsprIconB[iOldChoice]->PlayCommand( "LostSelectedLoseFocus" );
if( !bNewAlreadyHadFocus )
m_vsprIconB[iNewChoice]->PlayCommand( "LostSelectedGainFocus" );
}
else
{
if( !bOldStillHasFocus )
m_vsprIconB[iOldChoice]->PlayCommand( "LoseFocus" );
if( !bNewAlreadyHadFocus )
m_vsprIconB[iNewChoice]->PlayCommand( "GainFocus" );
}
}
else
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
// 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
{
// 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(m_bUsingTwoLists && m_iSelectedList == 1)
{
if( !bOldStillHasFocus )
m_vsprIconB[iOldChoice]->PlayCommand( "LoseFocus" );
if( !bNewAlreadyHadFocus )
m_vsprIconB[iNewChoice]->PlayCommand( "GainFocus" );
}
else
{
if( !bOldStillHasFocus )
m_vsprIcon[iOldChoice]->PlayCommand( "LoseFocus" );
if( !bNewAlreadyHadFocus )
m_vsprIcon[iNewChoice]->PlayCommand( "GainFocus" );
}
}
}
if( SHOW_CURSOR )
{
m_sprCursor[*p]->PlayCommand( "Change" );
m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) );
}
if( SHOW_SCROLLER )
{
ActorScroller &scroller = (m_bUsingTwoLists && m_iSelectedList == 1)?(SHARED_SELECTION ? m_ScrollerB[0] : m_ScrollerB[*p]):(SHARED_SELECTION ? m_Scroller[0] : m_Scroller[*p]);
vector<AutoActor> &vScroll = (m_bUsingTwoLists && m_iSelectedList == 1)?(SHARED_SELECTION ? m_vsprScrollB[0] : m_vsprScrollB[*p]):(SHARED_SELECTION ? m_vsprScroll[0] : m_vsprScroll[*p]);
// second list
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
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_aGameCommandsB.size();
fItem += iActualDir * iNumChoices;
scroller.SetCurrentAndDestinationItem( fItem );
}
}
scroller.SetDestinationItem( (float)iNewChoice );
}
else
{
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 )
{
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;
2010-01-26 21:00:30 -06:00
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;
2010-01-26 21:00:30 -06:00
if(SHOW_SCROLLER)
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
vector<AutoActor> &vScroll = SHARED_SELECTION ? m_vsprScrollB[0] : m_vsprScrollB[pn];
vScroll[m_iChoice[pn]]->PlayCommand( "InitialSelection" );
}
else
{
vector<AutoActor> &vScroll = SHARED_SELECTION ? m_vsprScroll[0] : m_vsprScroll[pn];
vScroll[m_iChoice[pn]]->PlayCommand( "InitialSelection" );
}
}
return;
}
const GameCommand &mc = (m_bUsingTwoLists && m_iSelectedList == 1)?m_aGameCommandsB[m_iChoice[pn]]: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;
2010-01-26 21:00:30 -06:00
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.
2010-01-26 21:00:30 -06:00
if( mc.m_sSoundPath.empty() && !m_bDoubleChoiceNoSound )
m_soundStart.PlayCopy();
if( mc.m_sScreen.empty() )
{
mc.ApplyToAllPlayers();
return;
}
2010-01-26 21:00:30 -06:00
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.
2010-01-26 21:00:30 -06:00
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 )
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". */
2010-01-26 21:00:30 -06:00
void ScreenSelectMaster::TweenOnScreen()
{
vector<PlayerNumber> vpns;
GetActiveElementPlayerNumbers( vpns );
if( SHOW_ICON )
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
for( unsigned c=0; c<m_aGameCommandsB.size(); c++ )
{
m_vsprIconB[c]->PlayCommand( (int(c) == m_iChoice[0])? "GainFocus":"LoseFocus" );
m_vsprIconB[c]->FinishTweening();
}
}
else
{
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 )
{
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
FOREACH( PlayerNumber, vpns, p )
{
// Play Gain/LoseFocus before playing the on command.
// Gain/Lose will often stop tweening, which ruins the OnCommand.
2010-01-26 21:00:30 -06:00
for( unsigned c=0; c<m_aGameCommands.size(); c++ )
{
m_vsprScrollB[*p][c]->PlayCommand( int(c) == m_iChoice[*p]? "GainFocus":"LoseFocus" );
m_vsprScrollB[*p][c]->FinishTweening();
}
m_ScrollerB[*p].SetCurrentAndDestinationItem( (float)m_iChoice[*p] );
}
}
else
{
FOREACH( PlayerNumber, vpns, p )
{
// Play Gain/LoseFocus before playing the on command.
// Gain/Lose will often stop tweening, which ruins the OnCommand.
2010-01-26 21:00:30 -06:00
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 )
{
m_sprCursor[*p]->SetXY( GetCursorX(*p), GetCursorY(*p) );
}
}
ScreenSelect::TweenOnScreen();
}
void ScreenSelectMaster::TweenOffScreen()
{
ScreenSelect::TweenOffScreen();
vector<PlayerNumber> vpns;
GetActiveElementPlayerNumbers( vpns );
// using two lists -- may need to consider throwing a wider range of commands
// for themers if tweening out with one/two lists/focused/not focused etc?
2010-01-26 21:00:30 -06:00
if(m_bUsingTwoLists && m_iSelectedList == 1)
{
for( unsigned c=0; c<m_aGameCommandsB.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_vsprIconB[c]->PlayCommand( bSelectedByEitherPlayer? "OffFocused":"OffUnfocused" );
if( SHOW_SCROLLER )
{
FOREACH( PlayerNumber, vpns, p )
m_vsprScrollB[*p][c]->PlayCommand( bSelectedByEitherPlayer? "OffFocused":"OffUnfocused" );
}
}
}
else
{
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"
class LunaScreenSelectMaster: public Luna<ScreenSelectMaster>
{
public:
static int GetSelectionIndex( T* p, lua_State *L ) { lua_pushnumber( L, p->GetSelectionIndexOfPlayer(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
2010-01-26 21:00:30 -06:00
/*
* (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.
*/