Files
itgmania212121/stepmania/src/PaneDisplay.cpp
T
Chris Danford bcbe615c0d Pass ActorCommand structures to Actor instead of unparsed command strings.
This way, we can potentially do the parsing early and not parse inside of Actor as the command is executing.
2004-11-06 23:13:47 +00:00

463 lines
14 KiB
C++

#include "global.h"
#include "PaneDisplay.h"
#include "ThemeManager.h"
#include "GameState.h"
#include "song.h"
#include "Steps.h"
#include "RageLog.h"
#include "ProfileManager.h"
#include "SongManager.h"
#include "Course.h"
#include "Style.h"
#define SHIFT_X(p) THEME->GetMetricF(m_sName, ssprintf("ShiftP%iX", p+1))
#define SHIFT_Y(p) THEME->GetMetricF(m_sName, ssprintf("ShiftP%iY", p+1))
#define NUM_ITEM_COLORS(s) THEME->GetMetricI(m_sName, ssprintf("%sNumLevels",s))
#define ITEM_COLOR(s,n) THEME->GetMetric (m_sName, ssprintf("%sLevel%i",s,n+1))
enum { NEED_NOTES=1, NEED_COURSE=2, NEED_PROFILE=4 };
struct Content_t
{
const char *name;
PaneTypes type;
int req;
};
static const Content_t g_Contents[NUM_PANE_CONTENTS] =
{
{ "SongNumSteps", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "SongJumps", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "SongHolds", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "SongMines", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "SongHands", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "DifficultyStream", NUM_PANES, NEED_NOTES }, // hide
{ "DifficultyChaos", NUM_PANES, NEED_NOTES },
{ "DifficultyFreeze", NUM_PANES, NEED_NOTES },
{ "DifficultyAir", NUM_PANES, NEED_NOTES },
{ "DifficultyVoltage", NUM_PANES, NEED_NOTES },
{ "MachineHighScore", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "MachineNumPlays", PANE_SONG_MACHINE_SCORES, NEED_NOTES },
{ "MachineRank", PANE_SONG_MACHINE_SCORES, NEED_NOTES },
{ "MachineHighName", PANE_SONG_DIFFICULTY, NEED_NOTES },
{ "ProfileHighScore", PANE_SONG_DIFFICULTY, NEED_NOTES|NEED_PROFILE },
{ "ProfileNumPlays", PANE_SONG_PROFILE_SCORES, NEED_NOTES|NEED_PROFILE },
{ "ProfileRank", PANE_SONG_PROFILE_SCORES, NEED_NOTES|NEED_PROFILE },
{ "CourseMachineHighScore", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseMachineNumPlays", NUM_PANES, NEED_COURSE },
{ "CourseMachineRank", NUM_PANES, NEED_COURSE },
{ "CourseMachineHighName", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseProfileHighScore", PANE_COURSE_MACHINE_SCORES, NEED_COURSE|NEED_PROFILE },
{ "CourseProfileNumPlays", NUM_PANES, NEED_COURSE|NEED_PROFILE },
{ "CourseProfileRank", NUM_PANES, NEED_COURSE|NEED_PROFILE },
{ "CourseNumSteps", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseJumps", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseHolds", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseMines", PANE_COURSE_MACHINE_SCORES, NEED_COURSE },
{ "CourseHands", PANE_COURSE_MACHINE_SCORES, NEED_COURSE }
};
static const PaneModes PaneMode[NUM_PANES] =
{
PANEMODE_SONG,
PANEMODE_SONG,
PANEMODE_SONG,
// PANEMODE_SONG,
PANEMODE_BATTLE,
PANEMODE_COURSE,
PANEMODE_COURSE
// PANEMODE_COURSE
};
static ProfileSlot PlayerMemCard( PlayerNumber pn )
{
switch( pn )
{
case PLAYER_1: return PROFILE_SLOT_PLAYER_1;
case PLAYER_2: return PROFILE_SLOT_PLAYER_2;
default: ASSERT(0); return PROFILE_SLOT_MACHINE;
};
}
PaneDisplay::PaneDisplay()
{
m_CurPane = PANE_INVALID;
m_CurMode = PANEMODE_SONG;
}
void PaneDisplay::Load( PlayerNumber pn )
{
m_PlayerNumber = pn;
m_PreferredPaneForMode[PANEMODE_SONG] = PANE_SONG_DIFFICULTY;
m_PreferredPaneForMode[PANEMODE_BATTLE] = PANE_BATTLE_DIFFICULTY;
m_PreferredPaneForMode[PANEMODE_COURSE] = PANE_COURSE_MACHINE_SCORES;
m_sprPaneUnder.Load( THEME->GetPathToG( ssprintf("PaneDisplay under p%i", pn+1)) );
m_sprPaneUnder->SetName( "Under" );
ON_COMMAND( m_sprPaneUnder );
this->AddChild( m_sprPaneUnder );
int p;
for( p = 0; p < NUM_PANE_CONTENTS; ++p )
{
if( g_Contents[p].type == NUM_PANES )
continue; /* skip, disabled */
m_textContents[p].LoadFromFont( THEME->GetPathToF( "PaneDisplay text" ) );
m_textContents[p].SetName( ssprintf("%sText", g_Contents[p].name) );
SET_XY_AND_ON_COMMAND( m_textContents[p] );
m_ContentsFrame.AddChild( &m_textContents[p] );
m_Labels[p].Load( THEME->GetPathToG( ssprintf("PaneDisplay %s label", g_Contents[p].name)) );
m_Labels[p]->SetName( ssprintf("%sLabel", g_Contents[p].name) );
SET_XY_AND_ON_COMMAND( m_Labels[p] );
m_ContentsFrame.AddChild( m_Labels[p] );
}
m_ContentsFrame.SetXY( SHIFT_X(m_PlayerNumber), SHIFT_Y(m_PlayerNumber) );
this->AddChild( &m_ContentsFrame );
m_sprPaneOver.Load( THEME->GetPathToG( ssprintf("PaneDisplay over p%i", pn+1)) );
m_sprPaneOver->SetName( "Over" );
ON_COMMAND( m_sprPaneOver );
this->AddChild( m_sprPaneOver );
for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
{
if( g_Contents[i].type == NUM_PANES )
continue; /* skip, disabled */
COMMAND( m_textContents[i], "LoseFocus" );
COMMAND( m_Labels[i], "LoseFocus" );
m_textContents[i].FinishTweening();
m_Labels[i]->FinishTweening();
}
m_CurMode = GetMode();
SetFocus( GetNext( m_PreferredPaneForMode[m_CurMode], 0 ) );
}
void PaneDisplay::Update( float fDeltaTime )
{
ActorFrame::Update( fDeltaTime );
}
void PaneDisplay::SetContent( PaneContents c )
{
CString str = "?"; // fill this in
float val = 0; // fill this in
const Song *pSong = GAMESTATE->m_pCurSong;
const Steps *pSteps = GAMESTATE->m_pCurSteps[m_PlayerNumber];
const Course *pCourse = GAMESTATE->m_pCurCourse;
const Trail *pTrail = GAMESTATE->m_pCurTrail[m_PlayerNumber];
const Profile *pProfile = PROFILEMAN->GetProfile( m_PlayerNumber );
bool bIsEdit = pSteps && pSteps->GetDifficulty() == DIFFICULTY_EDIT;
if( (g_Contents[c].req&NEED_NOTES) && !pSteps )
goto done;
if( (g_Contents[c].req&NEED_COURSE) && !pTrail )
goto done;
if( (g_Contents[c].req&NEED_PROFILE) && !pProfile )
{
str = "N/A";
goto done;
}
{
RadarValues rv;
if( g_Contents[c].req&NEED_NOTES )
rv = pSteps->GetRadarValues();
else if( g_Contents[c].req&NEED_COURSE )
rv = pTrail->GetRadarValues();
switch( c )
{
case COURSE_NUM_STEPS:
case SONG_NUM_STEPS: val = rv[RADAR_NUM_TAPS_AND_HOLDS]; break;
case COURSE_JUMPS:
case SONG_JUMPS: val = rv[RADAR_NUM_JUMPS]; break;
case COURSE_HOLDS:
case SONG_HOLDS: val = rv[RADAR_NUM_HOLDS]; break;
case COURSE_MINES:
case SONG_MINES: val = rv[RADAR_NUM_MINES]; break;
case COURSE_HANDS:
case SONG_HANDS: val = rv[RADAR_NUM_HANDS]; break;
case SONG_DIFFICULTY_RADAR_STREAM: val = rv[RADAR_STREAM]; break;
case SONG_DIFFICULTY_RADAR_VOLTAGE: val = rv[RADAR_VOLTAGE]; break;
case SONG_DIFFICULTY_RADAR_AIR: val = rv[RADAR_AIR]; break;
case SONG_DIFFICULTY_RADAR_FREEZE: val = rv[RADAR_FREEZE]; break;
case SONG_DIFFICULTY_RADAR_CHAOS: val = rv[RADAR_CHAOS]; break;
case SONG_PROFILE_HIGH_SCORE:
val = 100.0f * PROFILEMAN->GetProfile(m_PlayerNumber)->GetStepsHighScoreList(pSong,pSteps).GetTopScore().fPercentDP;
break;
case SONG_PROFILE_NUM_PLAYS:
val = (float) PROFILEMAN->GetProfile(m_PlayerNumber)->GetStepsNumTimesPlayed(pSong,pSteps);
break;
case SONG_MACHINE_HIGH_NAME: /* set val for color */
case SONG_MACHINE_HIGH_SCORE:
CHECKPOINT;
if( bIsEdit )
goto done; // no machine scores for edits
val = 100.0f * PROFILEMAN->GetMachineProfile()->GetStepsHighScoreList(pSong,pSteps).GetTopScore().fPercentDP;
break;
case SONG_MACHINE_RANK:
{
const vector<Song*> best = SONGMAN->GetBestSongs( PROFILE_SLOT_MACHINE );
val = (float) FindIndex( best.begin(), best.end(), pSong );
val += 1;
break;
}
case SONG_PROFILE_RANK:
{
const vector<Song*> best = SONGMAN->GetBestSongs( PlayerMemCard(m_PlayerNumber) );
val = (float) FindIndex( best.begin(), best.end(), pSong );
val += 1;
break;
}
case COURSE_MACHINE_HIGH_NAME: /* set val for color */
case COURSE_MACHINE_HIGH_SCORE:
val = 100.0f * PROFILEMAN->GetMachineProfile()->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().fPercentDP;
break;
case COURSE_MACHINE_NUM_PLAYS:
val = (float) PROFILEMAN->GetMachineProfile()->GetCourseNumTimesPlayed( pCourse );
break;
case COURSE_MACHINE_RANK:
{
const vector<Course*> best = SONGMAN->GetBestCourses( PROFILE_SLOT_MACHINE );
val = (float) FindIndex( best.begin(), best.end(), pCourse );
val += 1;
}
break;
case COURSE_PROFILE_HIGH_SCORE:
val = 100.0f * PROFILEMAN->GetProfile(m_PlayerNumber)->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().fPercentDP;
break;
case COURSE_PROFILE_NUM_PLAYS:
val = (float) PROFILEMAN->GetProfile(m_PlayerNumber)->GetCourseNumTimesPlayed( pCourse );
break;
case COURSE_PROFILE_RANK:
const vector<Course*> best = SONGMAN->GetBestCourses( PlayerMemCard(m_PlayerNumber) );
val = (float) FindIndex( best.begin(), best.end(), pCourse );
val += 1;
break;
};
if( val == RADAR_VAL_UNKNOWN )
goto done;
/* Scale, round, clamp, etc. for floats: */
switch( c )
{
case SONG_DIFFICULTY_RADAR_STREAM:
case SONG_DIFFICULTY_RADAR_VOLTAGE:
case SONG_DIFFICULTY_RADAR_AIR:
case SONG_DIFFICULTY_RADAR_FREEZE:
case SONG_DIFFICULTY_RADAR_CHAOS:
val = roundf( SCALE( val, 0, 1, 0, 10 ) );
val = clamp( val, 0, 10 );
str = ssprintf( "%.0f", val );
break;
}
switch( c )
{
case SONG_MACHINE_HIGH_NAME:
str = PROFILEMAN->GetMachineProfile()->GetStepsHighScoreList(pSong,pSteps).GetTopScore().sName;
break;
case COURSE_MACHINE_HIGH_NAME:
str = PROFILEMAN->GetMachineProfile()->GetCourseHighScoreList(pCourse,pTrail).GetTopScore().sName;
break;
case SONG_MACHINE_HIGH_SCORE:
case COURSE_MACHINE_HIGH_SCORE:
case SONG_PROFILE_HIGH_SCORE:
case COURSE_PROFILE_HIGH_SCORE:
str = ssprintf( "%.2f%%", val );
break;
case SONG_NUM_STEPS:
case SONG_JUMPS:
case SONG_HOLDS:
case SONG_MINES:
case SONG_HANDS:
case COURSE_NUM_STEPS:
case COURSE_JUMPS:
case COURSE_HOLDS:
case COURSE_MINES:
case COURSE_HANDS:
case SONG_MACHINE_NUM_PLAYS:
case COURSE_MACHINE_NUM_PLAYS:
case SONG_PROFILE_NUM_PLAYS:
case COURSE_PROFILE_NUM_PLAYS:
case SONG_MACHINE_RANK:
case COURSE_MACHINE_RANK:
case SONG_PROFILE_RANK:
case COURSE_PROFILE_RANK:
str = ssprintf( "%.0f", val );
}
}
done:
m_textContents[c].SetText( str );
const int num = NUM_ITEM_COLORS( g_Contents[c].name );
for( int p = 0; p < num; ++p )
{
const CString metric = ITEM_COLOR(g_Contents[c].name, p);
CStringArray spec;
split( metric, ";", spec );
if( spec.size() < 2 )
RageException::Throw( "Metric '%s' malformed", metric.c_str() );
const float n = strtof( spec[0], NULL );
if( val >= n )
continue;
spec.erase( spec.begin(), spec.begin()+1 );
m_textContents[c].Command( ParseActorCommands(join(";", spec)) );
break;
}
}
void PaneDisplay::SetFromGameState()
{
m_CurMode = GetMode();
if( PaneMode[m_CurPane] != m_CurMode )
SetFocus( GetNext( m_PreferredPaneForMode[m_CurMode], 0 ) );
/* Don't update text that doesn't apply to the current mode. It's still tweening off. */
for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
{
if( g_Contents[i].type != m_CurPane )
continue;
SetContent( (PaneContents) i );
}
}
PaneModes PaneDisplay::GetMode() const
{
switch( GAMESTATE->m_SortOrder )
{
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
return PANEMODE_COURSE;
case SORT_SORT_MENU:
case SORT_MODE_MENU:
return m_CurMode; // leave it
default:
return PANEMODE_SONG;
}
}
void PaneDisplay::SetFocus( PaneTypes NewPane )
{
if( m_CurPane == NewPane )
return;
for( unsigned i = 0; i < NUM_PANE_CONTENTS; ++i )
{
if( g_Contents[i].type == NUM_PANES )
continue; /* skip, disabled */
if( g_Contents[i].type == m_CurPane )
{
COMMAND( m_textContents[i], "LoseFocus" );
COMMAND( m_Labels[i], "LoseFocus" );
}
else if( g_Contents[i].type == NewPane )
{
COMMAND( m_textContents[i], "GainFocus" );
COMMAND( m_Labels[i], "GainFocus" );
}
}
m_CurPane = NewPane;
m_PreferredPaneForMode[m_CurMode] = NewPane;
SetFromGameState();
}
bool PaneDisplay::PaneIsValid( PaneTypes p ) const
{
if( PaneMode[p] != m_CurMode )
return false;
switch( p )
{
case PANE_SONG_PROFILE_SCORES:
case PANE_COURSE_PROFILE_SCORES:
if( !PROFILEMAN->IsUsingProfile( m_PlayerNumber ) )
return false;
}
return true;
}
PaneTypes PaneDisplay::GetNext( PaneTypes current, int dir ) const
{
if( dir == 0 )
{
/* Only move the selection if the current selection is invalid. */
if( PaneIsValid( current ) )
return current;
dir = 1;
}
PaneTypes ret = current;
while( 1 )
{
ret = (PaneTypes) (ret + dir);
wrap( (int&) ret, NUM_PANES );
if( PaneIsValid( ret ) )
break;
}
LOG->Trace("pane %i", ret);
return ret;
}
void PaneDisplay::Move( int dir )
{
const PaneTypes NewPane = GetNext( m_CurPane, dir );
SetFocus( NewPane );
}
/*
* (c) 2003 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.
*/