2003-02-16 04:01:45 +00:00
#include "global.h"
2003-04-10 05:46:31 +00:00
#include "Player.h"
2002-05-01 19:14:55 +00:00
#include "GameConstantsAndTypes.h"
2001-11-04 19:34:28 +00:00
#include "RageUtil.h"
2002-07-23 01:41:40 +00:00
#include "PrefsManager.h"
2002-05-01 19:14:55 +00:00
#include "GameConstantsAndTypes.h"
2002-04-16 17:31:00 +00:00
#include "GameManager.h"
#include "InputMapper.h"
2002-06-14 22:25:22 +00:00
#include "SongManager.h"
2002-07-23 01:41:40 +00:00
#include "GameState.h"
2002-11-03 07:17:03 +00:00
#include "ScoreKeeperMAX2.h"
2002-07-28 20:28:37 +00:00
#include "RageLog.h"
2002-10-28 05:30:45 +00:00
#include "RageMath.h"
2002-11-11 04:53:31 +00:00
#include "RageDisplay.h"
#include "ThemeManager.h"
2002-11-07 19:42:39 +00:00
#include "Combo.h"
2003-02-25 02:51:04 +00:00
#include "ScoreDisplay.h"
#include "LifeMeter.h"
2003-11-11 07:36:28 +00:00
#include "CombinedLifeMeter.h"
2003-04-07 22:07:44 +00:00
#include "PlayerAI.h"
2003-04-14 22:32:08 +00:00
#include "NoteFieldPositioning.h"
2003-08-07 06:36:34 +00:00
#include "NoteDataUtil.h"
2003-09-06 02:20:09 +00:00
#include "ScreenGameplay.h" /* for SM_ComboStopped */
#include "ScreenManager.h"
2003-12-23 00:26:00 +00:00
#include "StageStats.h"
2004-02-25 07:30:12 +00:00
#include "ArrowEffects.h"
2004-07-25 17:07:32 +00:00
#include "Game.h"
2004-08-10 06:39:25 +00:00
#include "NetworkSyncManager.h" //used for sending timing offset
2004-08-19 01:18:11 +00:00
#include "DancingCharacters.h"
2004-09-21 06:07:12 +00:00
#include "ScreenDimensions.h"
2001-11-03 10:52:42 +00:00
2003-12-22 10:30:10 +00:00
CachedThemeMetricF GRAY_ARROWS_Y_STANDARD ( "Player" , "ReceptorArrowsYStandard" );
CachedThemeMetricF GRAY_ARROWS_Y_REVERSE ( "Player" , "ReceptorArrowsYReverse" );
2004-08-07 02:13:52 +00:00
#define JUDGMENT_X( p, both_sides ) THEME->GetMetricF("Player",both_sides ? CString("JudgmentXOffsetBothSides") : ssprintf("JudgmentXOffsetOneSideP%d",p+1))
2004-03-24 07:05:50 +00:00
#define JUDGMENT_Y THEME->GetMetricF("Player","JudgmentY")
#define JUDGMENT_Y_REVERSE THEME->GetMetricF("Player","JudgmentYReverse")
2004-08-07 02:13:52 +00:00
#define COMBO_X( p, both_sides ) THEME->GetMetricF("Player",both_sides ? CString("ComboXOffsetBothSides") : ssprintf("ComboXOffsetOneSideP%d",p+1))
2004-03-24 07:05:50 +00:00
#define COMBO_Y THEME->GetMetricF("Player","ComboY")
#define COMBO_Y_REVERSE THEME->GetMetricF("Player","ComboYReverse")
2004-08-07 02:13:52 +00:00
#define ATTACK_DISPLAY_X( p, both_sides ) THEME->GetMetricF("Player",both_sides ? CString("AttackDisplayXOffsetBothSides") : ssprintf("AttackDisplayXOffsetOneSideP%d",p+1))
2004-03-24 07:05:50 +00:00
#define ATTACK_DISPLAY_Y THEME->GetMetricF("Player","AttackDisplayY")
#define ATTACK_DISPLAY_Y_REVERSE THEME->GetMetricF("Player","AttackDisplayYReverse")
2003-08-14 08:13:08 +00:00
CachedThemeMetricF HOLD_JUDGMENT_Y_STANDARD ( "Player" , "HoldJudgmentYStandard" );
CachedThemeMetricF HOLD_JUDGMENT_Y_REVERSE ( "Player" , "HoldJudgmentYReverse" );
CachedThemeMetricI BRIGHT_GHOST_COMBO_THRESHOLD ( "Player" , "BrightGhostComboThreshold" );
2003-11-25 06:45:06 +00:00
CachedThemeMetricB TAP_JUDGMENTS_UNDER_FIELD ( "Player" , "TapJudgmentsUnderField" );
CachedThemeMetricB HOLD_JUDGMENTS_UNDER_FIELD ( "Player" , "HoldJudgmentsUnderField" );
2004-03-24 07:05:50 +00:00
#define START_DRAWING_AT_PIXELS THEME->GetMetricI("Player","StartDrawingAtPixels")
#define STOP_DRAWING_AT_PIXELS THEME->GetMetricI("Player","StopDrawingAtPixels")
#define MAX_PRO_TIMING_ERROR THEME->GetMetricI("Player","MaxProTimingError")
2001-11-04 19:34:28 +00:00
2003-03-26 22:22:10 +00:00
/* Distance to search for a note in Step(). */
2003-04-07 22:07:44 +00:00
/* Units? */
2003-03-26 22:22:10 +00:00
static const float StepSearchDistanceBackwards = 1.0f ;
static const float StepSearchDistanceForwards = 1.0f ;
2002-02-24 01:43:11 +00:00
2004-06-27 00:03:06 +00:00
#define ADJUSTED_WINDOW( judge ) ((PREFSMAN->m_fJudgeWindowSeconds##judge * PREFSMAN->m_fJudgeWindowScale) + PREFSMAN->m_fJudgeWindowAdd)
2003-04-07 21:24:14 +00:00
2003-08-24 17:04:21 +00:00
PlayerMinus :: PlayerMinus ()
2001-11-03 10:52:42 +00:00
{
2003-08-14 08:13:08 +00:00
GRAY_ARROWS_Y_STANDARD . Refresh ();
GRAY_ARROWS_Y_REVERSE . Refresh ();
HOLD_JUDGMENT_Y_STANDARD . Refresh ();
HOLD_JUDGMENT_Y_REVERSE . Refresh ();
2003-02-17 12:19:42 +00:00
BRIGHT_GHOST_COMBO_THRESHOLD . Refresh ();
2003-11-25 06:45:06 +00:00
TAP_JUDGMENTS_UNDER_FIELD . Refresh ();
HOLD_JUDGMENTS_UNDER_FIELD . Refresh ();
2004-07-02 19:06:11 +00:00
2002-06-24 22:04:31 +00:00
m_PlayerNumber = PLAYER_INVALID ;
2004-02-25 07:30:12 +00:00
m_fNoteFieldHeight = 0 ;
2001-11-03 10:52:42 +00:00
2002-04-28 20:42:32 +00:00
m_pLifeMeter = NULL ;
2003-06-30 18:08:27 +00:00
m_pCombinedLifeMeter = NULL ;
2003-11-26 06:40:03 +00:00
m_pScoreDisplay = NULL ;
m_pSecondaryScoreDisplay = NULL ;
2003-06-30 18:08:27 +00:00
m_pPrimaryScoreKeeper = NULL ;
m_pSecondaryScoreKeeper = NULL ;
2003-02-25 02:51:04 +00:00
m_pInventory = NULL ;
2002-11-03 07:17:03 +00:00
2004-04-15 05:41:57 +00:00
m_iOffsetSample = 0 ;
2003-04-14 23:09:35 +00:00
this -> AddChild ( & m_ArrowBackdrop );
2003-02-17 12:19:42 +00:00
this -> AddChild ( & m_Judgment );
2003-08-01 01:18:08 +00:00
this -> AddChild ( & m_ProTimingDisplay );
2003-02-17 12:19:42 +00:00
this -> AddChild ( & m_Combo );
2003-11-26 08:25:45 +00:00
this -> AddChild ( & m_AttackDisplay );
2002-04-16 17:31:00 +00:00
for ( int c = 0 ; c < MAX_NOTE_TRACKS ; c ++ )
2003-02-17 12:19:42 +00:00
this -> AddChild ( & m_HoldJudgment [ c ] );
2003-04-21 02:41:10 +00:00
2003-08-20 09:58:11 +00:00
2003-04-21 02:41:10 +00:00
PlayerAI :: InitFromDisk ();
2001-11-03 10:52:42 +00:00
}
2003-08-24 17:04:21 +00:00
PlayerMinus ::~ PlayerMinus ()
2002-11-03 07:17:03 +00:00
{
}
2003-11-27 05:23:33 +00:00
void PlayerMinus :: Load ( PlayerNumber pn , const NoteData * pNoteData , LifeMeter * pLM , CombinedLifeMeter * pCombinedLM , ScoreDisplay * pScoreDisplay , ScoreDisplay * pSecondaryScoreDisplay , Inventory * pInventory , ScoreKeeper * pPrimaryScoreKeeper , ScoreKeeper * pSecondaryScoreKeeper , NoteField * pNoteField )
2002-02-02 05:11:12 +00:00
{
2003-11-10 23:25:44 +00:00
m_iDCState = AS2D_IDLE ;
2003-08-24 17:04:21 +00:00
//LOG->Trace( "PlayerMinus::Load()", );
2002-07-28 20:28:37 +00:00
2003-10-26 03:02:30 +00:00
GAMESTATE -> ResetNoteSkinsForPlayer ( pn );
2002-07-28 20:28:37 +00:00
m_PlayerNumber = pn ;
m_pLifeMeter = pLM ;
2003-06-30 18:08:27 +00:00
m_pCombinedLifeMeter = pCombinedLM ;
2003-11-26 06:40:03 +00:00
m_pScoreDisplay = pScoreDisplay ;
m_pSecondaryScoreDisplay = pSecondaryScoreDisplay ;
2003-02-25 02:51:04 +00:00
m_pInventory = pInventory ;
2003-06-30 18:08:27 +00:00
m_pPrimaryScoreKeeper = pPrimaryScoreKeeper ;
m_pSecondaryScoreKeeper = pSecondaryScoreKeeper ;
2003-08-24 17:04:21 +00:00
m_pNoteField = pNoteField ;
2004-02-25 05:49:54 +00:00
m_iRowLastCrossed = BeatToNoteRowNotRounded ( GAMESTATE -> m_fSongBeat ) - 1 ; // why this?
m_iMineRowLastCrossed = BeatToNoteRowNotRounded ( GAMESTATE -> m_fSongBeat ) - 1 ; // why this?
2002-07-28 20:28:37 +00:00
2003-04-15 02:52:58 +00:00
/* Ensure that this is up-to-date. */
2003-04-21 23:43:51 +00:00
GAMESTATE -> m_pPosition -> Load ( pn );
2003-04-15 02:52:58 +00:00
2004-06-28 07:26:00 +00:00
const Style * pStyle = GAMESTATE -> GetCurrentStyle ();
2002-07-28 20:28:37 +00:00
2002-08-01 21:55:40 +00:00
// init scoring
2002-12-18 22:25:24 +00:00
NoteDataWithScoring :: Init ();
2002-08-01 21:55:40 +00:00
2002-07-28 20:28:37 +00:00
// copy note data
2002-04-16 17:31:00 +00:00
this -> CopyAll ( pNoteData );
2003-12-23 00:26:00 +00:00
if ( GAMESTATE -> m_SongOptions . m_LifeType == SongOptions :: LIFE_BATTERY && g_CurStageStats . bFailed [ pn ] ) // Oni dead
2002-08-01 21:55:40 +00:00
this -> ClearAll ();
2002-07-28 20:28:37 +00:00
2002-08-26 08:11:52 +00:00
/* The editor reuses Players ... so we really need to make sure everything
* is reset and not tweening. Perhaps ActorFrame should recurse to subactors;
* then we could just this->StopTweening()? -glenn */
2003-02-17 12:19:42 +00:00
m_Judgment . StopTweening ();
2003-05-19 08:55:33 +00:00
// m_Combo.Reset(); // don't reset combos between songs in a course!
2003-02-25 00:33:42 +00:00
m_Combo . Init ( pn );
2004-07-31 21:26:11 +00:00
m_Combo . SetCombo ( g_CurStageStats . iCurCombo [ m_PlayerNumber ], g_CurStageStats . iCurMissCombo [ m_PlayerNumber ] ); // combo can persist between songs and games
2003-11-26 08:25:45 +00:00
m_AttackDisplay . Init ( pn );
2003-02-17 12:19:42 +00:00
m_Judgment . Reset ();
2002-07-28 20:28:37 +00:00
2003-10-10 23:44:17 +00:00
/* Don't re-init this; that'll reload graphics. Add a separate Reset() call
* if some ScoreDisplays need it. */
// if( m_pScore )
// m_pScore->Init( pn );
2002-04-28 20:42:32 +00:00
2003-08-13 19:17:28 +00:00
/* Apply transforms. */
2004-06-28 07:26:00 +00:00
NoteDataUtil :: TransformNoteData ( * this , GAMESTATE -> m_PlayerOptions [ pn ], GAMESTATE -> GetCurrentStyle () -> m_StepsType );
2003-11-20 06:50:05 +00:00
switch ( GAMESTATE -> m_PlayMode )
{
2003-11-26 06:40:03 +00:00
case PLAY_MODE_RAVE :
2003-11-20 06:50:05 +00:00
case PLAY_MODE_BATTLE :
{
// ugly, ugly, ugly. Works only w/ dance.
2004-06-28 07:26:00 +00:00
NoteDataUtil :: TransformNoteData ( * this , GAMESTATE -> m_PlayerOptions [ pn ], GAMESTATE -> GetCurrentStyle () -> m_StepsType );
2003-11-20 06:50:05 +00:00
// shuffle either p1 or p2
static int count = 0 ;
switch ( count )
{
case 0 :
case 3 :
2004-05-16 02:51:55 +00:00
NoteDataUtil :: Turn ( * this , STEPS_TYPE_DANCE_SINGLE , NoteDataUtil :: left );
2003-11-20 06:50:05 +00:00
break ;
case 1 :
case 2 :
2004-05-16 02:51:55 +00:00
NoteDataUtil :: Turn ( * this , STEPS_TYPE_DANCE_SINGLE , NoteDataUtil :: right );
2003-11-20 06:50:05 +00:00
break ;
default :
ASSERT ( 0 );
}
count ++ ;
count %= 4 ;
}
break ;
}
2001-11-04 19:34:28 +00:00
2003-02-19 06:42:29 +00:00
int iStartDrawingAtPixels = GAMESTATE -> m_bEditing ? - 100 : START_DRAWING_AT_PIXELS ;
int iStopDrawingAtPixels = GAMESTATE -> m_bEditing ? 400 : STOP_DRAWING_AT_PIXELS ;
2002-08-31 03:04:38 +00:00
2003-04-14 22:32:08 +00:00
m_ArrowBackdrop . Unload ();
2003-04-17 21:16:56 +00:00
CString BackdropName = g_NoteFieldMode [ pn ]. m_Backdrop ;
2003-04-14 22:32:08 +00:00
if ( ! BackdropName . empty () )
m_ArrowBackdrop . LoadFromAniDir ( THEME -> GetPathToB ( BackdropName ) );
2003-05-04 13:58:45 +00:00
2003-09-21 18:07:29 +00:00
float fNoteFieldMidde = ( GRAY_ARROWS_Y_STANDARD + GRAY_ARROWS_Y_REVERSE ) / 2 ;
2004-10-01 02:11:52 +00:00
#if defined(XBOX)
/* HACK: The receptor arrows are offset down and to the right. This sets them back.
* It doesn't look perfect but its better than having player 2's arrows of the screen.
*/
m_pNoteField -> SetY ( fNoteFieldMidde - 20 );
m_pNoteField -> SetX ( m_pNoteField -> GetX () - 55 );
#else
2003-09-21 18:07:29 +00:00
m_pNoteField -> SetY ( fNoteFieldMidde );
2004-10-01 02:11:52 +00:00
#endif
2004-02-25 07:30:12 +00:00
m_fNoteFieldHeight = GRAY_ARROWS_Y_REVERSE - GRAY_ARROWS_Y_STANDARD ;
m_pNoteField -> Load ( this , pn , iStartDrawingAtPixels , iStopDrawingAtPixels , m_fNoteFieldHeight );
2003-04-14 22:32:08 +00:00
m_ArrowBackdrop . SetPlayer ( pn );
2002-02-24 01:43:11 +00:00
2003-08-17 00:15:54 +00:00
const bool bReverse = GAMESTATE -> m_PlayerOptions [ pn ]. GetReversePercentForColumn ( 0 ) == 1 ;
2004-06-28 07:26:00 +00:00
bool bPlayerUsingBothSides = GAMESTATE -> GetCurrentStyle () -> m_StyleType == Style :: ONE_PLAYER_TWO_CREDITS ;
2003-04-14 22:12:54 +00:00
m_Combo . SetX ( COMBO_X ( m_PlayerNumber , bPlayerUsingBothSides ) );
2003-10-16 09:21:12 +00:00
m_Combo . SetY ( bReverse ? COMBO_Y_REVERSE : COMBO_Y );
2004-10-01 02:11:52 +00:00
m_AttackDisplay . SetX ( ATTACK_DISPLAY_X ( m_PlayerNumber , bPlayerUsingBothSides ) - 40 );
2003-11-26 08:25:45 +00:00
m_AttackDisplay . SetY ( bReverse ? ATTACK_DISPLAY_Y_REVERSE : ATTACK_DISPLAY_Y );
2003-04-14 22:12:54 +00:00
m_Judgment . SetX ( JUDGMENT_X ( m_PlayerNumber , bPlayerUsingBothSides ) );
2003-10-16 09:21:12 +00:00
m_Judgment . SetY ( bReverse ? JUDGMENT_Y_REVERSE : JUDGMENT_Y );
2003-08-01 01:18:08 +00:00
m_ProTimingDisplay . SetX ( JUDGMENT_X ( m_PlayerNumber , bPlayerUsingBothSides ) );
m_ProTimingDisplay . SetY ( bReverse ? SCREEN_BOTTOM - JUDGMENT_Y : SCREEN_TOP + JUDGMENT_Y );
2002-02-24 01:43:11 +00:00
2003-04-17 22:53:36 +00:00
/* These commands add to the above positioning, and are usually empty. */
m_Judgment . Command ( g_NoteFieldMode [ pn ]. m_JudgmentCmd );
2003-08-01 01:18:08 +00:00
m_ProTimingDisplay . Command ( g_NoteFieldMode [ pn ]. m_JudgmentCmd );
2003-04-17 22:53:36 +00:00
m_Combo . Command ( g_NoteFieldMode [ pn ]. m_ComboCmd );
2003-11-26 08:25:45 +00:00
m_AttackDisplay . Command ( g_NoteFieldMode [ pn ]. m_AttackDisplayCmd );
2003-04-17 22:53:36 +00:00
2002-11-16 09:12:55 +00:00
int c ;
2004-06-28 07:26:00 +00:00
for ( c = 0 ; c < pStyle -> m_iColsPerPlayer ; c ++ )
2003-02-17 12:19:42 +00:00
{
2004-06-19 23:04:10 +00:00
NoteFieldMode & mode = g_NoteFieldMode [ pn ];
m_HoldJudgment [ c ]. Command ( mode . m_HoldJudgmentCmd [ c ] );
2003-02-17 12:19:42 +00:00
}
2002-02-24 01:43:11 +00:00
2003-08-14 08:13:08 +00:00
// Need to set Y positions of all these elements in Update since
// they change depending on PlayerOptions.
2003-04-22 04:54:04 +00:00
2004-07-27 05:12:13 +00:00
RageSoundParams p ;
2004-02-28 04:14:13 +00:00
m_soundMine . Load ( THEME -> GetPathToS ( "Player mine" ), true );
2004-04-08 03:40:28 +00:00
/* Attacks can be launched in course modes and in battle modes. They both come
* here to play, but allow loading a different sound for different modes. */
switch ( GAMESTATE -> m_PlayMode )
{
case PLAY_MODE_RAVE :
case PLAY_MODE_BATTLE :
2004-07-27 05:12:13 +00:00
m_soundAttackLaunch . Load ( THEME -> GetPathToS ( "Player battle attack launch" ), true );
m_soundAttackEnding . Load ( THEME -> GetPathToS ( "Player battle attack ending" ), true );
2004-04-08 03:40:28 +00:00
break ;
default :
2004-07-27 05:12:13 +00:00
m_soundAttackLaunch . Load ( THEME -> GetPathToS ( "Player course attack launch" ), true );
m_soundAttackEnding . Load ( THEME -> GetPathToS ( "Player course attack ending" ), true );
2004-04-08 03:40:28 +00:00
break ;
}
2004-07-27 05:12:13 +00:00
if ( GAMESTATE -> GetNumPlayersEnabled () == 2 )
{
/* Two players are active. Play sounds on this player's side. */
p . m_Balance = ( m_PlayerNumber == PLAYER_1 ) ? - 1.0f : 1.0f ;
}
m_soundMine . SetParams ( p );
m_soundAttackLaunch . SetParams ( p );
m_soundAttackEnding . SetParams ( p );
2001-11-04 19:34:28 +00:00
}
2003-08-24 17:04:21 +00:00
void PlayerMinus :: Update ( float fDeltaTime )
2001-11-03 10:52:42 +00:00
{
2003-08-24 17:04:21 +00:00
//LOG->Trace( "PlayerMinus::Update(%f)", fDeltaTime );
2001-11-03 10:52:42 +00:00
2003-04-13 04:50:08 +00:00
if ( GAMESTATE -> m_pCurSong == NULL )
return ;
2004-03-24 07:37:35 +00:00
if ( GAMESTATE -> m_bAttackBeganThisUpdate [ m_PlayerNumber ] )
m_soundAttackLaunch . Play ();
2003-11-27 02:30:54 +00:00
if ( GAMESTATE -> m_bAttackEndedThisUpdate [ m_PlayerNumber ] )
2003-11-20 06:50:05 +00:00
m_soundAttackEnding . Play ();
2002-07-28 20:28:37 +00:00
const float fSongBeat = GAMESTATE -> m_fSongBeat ;
2003-12-16 04:00:39 +00:00
const int iSongRow = BeatToNoteRow ( fSongBeat );
2002-06-23 11:43:53 +00:00
2003-08-24 17:04:21 +00:00
m_pNoteField -> Update ( fDeltaTime );
2003-08-14 08:13:08 +00:00
//
// Update Y positions
//
2004-02-25 07:30:12 +00:00
{
2004-06-28 07:26:00 +00:00
for ( int c = 0 ; c < GAMESTATE -> GetCurrentStyle () -> m_iColsPerPlayer ; c ++ )
2004-02-25 07:30:12 +00:00
{
float fPercentReverse = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. GetReversePercentForColumn ( c );
float fHoldJudgeYPos = SCALE ( fPercentReverse , 0.f , 1.f , HOLD_JUDGMENT_Y_STANDARD , HOLD_JUDGMENT_Y_REVERSE );
2004-02-26 03:18:51 +00:00
// float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE );
2004-02-25 07:30:12 +00:00
const float fX = ArrowGetXPos ( m_PlayerNumber , c , 0 );
const float fZ = ArrowGetZPos ( m_PlayerNumber , c , 0 );
m_HoldJudgment [ c ]. SetX ( fX );
m_HoldJudgment [ c ]. SetY ( fHoldJudgeYPos );
m_HoldJudgment [ c ]. SetZ ( fZ );
}
}
2003-08-17 00:15:54 +00:00
float fPercentReverse = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. GetReversePercentForColumn ( 0 );
2003-08-14 08:13:08 +00:00
float fGrayYPos = SCALE ( fPercentReverse , 0.f , 1.f , GRAY_ARROWS_Y_STANDARD , GRAY_ARROWS_Y_REVERSE );
m_ArrowBackdrop . SetY ( fGrayYPos );
2003-08-16 23:34:47 +00:00
// NoteField accounts for reverse on its own now.
2003-08-24 17:04:21 +00:00
// m_pNoteField->SetY( fGrayYPos );
2003-08-14 08:13:08 +00:00
float fMiniPercent = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. m_fEffects [ PlayerOptions :: EFFECT_MINI ];
2004-03-25 09:31:38 +00:00
float fNoteFieldZoom = 1 - fMiniPercent * 0.5f ;
float fJudgmentZoom = 1 - fMiniPercent * 0.25f ;
m_pNoteField -> SetZoom ( fNoteFieldZoom );
m_Judgment . SetZoom ( fJudgmentZoom );
2003-08-14 08:13:08 +00:00
2002-02-24 01:43:11 +00:00
//
2002-03-19 07:09:49 +00:00
// Check for TapNote misses
2002-02-24 01:43:11 +00:00
//
2004-05-24 04:26:54 +00:00
UpdateTapNotesMissedOlderThan ( GetMaxStepDistanceSeconds () );
2001-11-04 19:34:28 +00:00
2003-12-22 10:30:10 +00:00
//
// update pressed flag
//
2004-06-28 07:26:00 +00:00
const int iNumCols = GAMESTATE -> GetCurrentStyle () -> m_iColsPerPlayer ;
2004-05-31 01:03:45 +00:00
ASSERT_M ( iNumCols < MAX_COLS_PER_PLAYER , ssprintf ( "%i >= %i" , iNumCols , MAX_COLS_PER_PLAYER ) );
for ( int col = 0 ; col < iNumCols ; ++ col )
2003-10-09 03:50:44 +00:00
{
2004-05-31 01:03:45 +00:00
CHECKPOINT_M ( ssprintf ( "%i %i" , col , iNumCols ) );
const StyleInput StyleI ( m_PlayerNumber , col );
2004-06-28 07:26:00 +00:00
const GameInput GameI = GAMESTATE -> GetCurrentStyle () -> StyleInputToGameInput ( StyleI );
2003-10-09 03:50:44 +00:00
bool bIsHoldingButton = INPUTMAPPER -> IsButtonDown ( GameI );
2004-01-01 04:06:37 +00:00
// TODO: Make this work for non-human-controlled players
if ( bIsHoldingButton && ! GAMESTATE -> m_bDemonstrationOrJukebox && GAMESTATE -> m_PlayerController [ m_PlayerNumber ] == PC_HUMAN )
2004-05-31 01:03:45 +00:00
m_pNoteField -> SetPressed ( col );
2003-10-09 03:50:44 +00:00
}
2004-05-30 22:55:05 +00:00
2002-02-24 01:43:11 +00:00
//
2002-03-19 07:09:49 +00:00
// update HoldNotes logic
2002-02-24 01:43:11 +00:00
//
2002-11-02 22:46:15 +00:00
for ( int i = 0 ; i < GetNumHoldNotes (); i ++ ) // for each HoldNote
2001-12-19 01:50:57 +00:00
{
2002-11-02 22:46:15 +00:00
const HoldNote & hn = GetHoldNote ( i );
2003-12-16 06:10:50 +00:00
HoldNoteScore hns = GetHoldNoteScore ( hn );
2002-08-13 23:26:46 +00:00
2003-12-18 02:34:59 +00:00
m_pNoteField -> m_HeldHoldNotes [ hn ] = false ; // set hold flag so NoteField can do intelligent drawing
2004-01-01 01:53:25 +00:00
m_pNoteField -> m_ActiveHoldNotes [ hn ] = false ; // set hold flag so NoteField can do intelligent drawing
2002-08-13 23:26:46 +00:00
2002-02-03 20:45:08 +00:00
2002-06-14 22:25:22 +00:00
if ( hns != HNS_NONE ) // if this HoldNote already has a result
2002-02-24 01:43:11 +00:00
continue ; // we don't need to update the logic for this one
2003-12-16 06:10:50 +00:00
if ( iSongRow < hn . iStartRow )
continue ; // hold hasn't happened yet
2003-02-16 20:27:53 +00:00
const StyleInput StyleI ( m_PlayerNumber , hn . iTrack );
2004-06-28 07:26:00 +00:00
const GameInput GameI = GAMESTATE -> GetCurrentStyle () -> StyleInputToGameInput ( StyleI );
2002-02-24 01:43:11 +00:00
2002-10-12 22:04:44 +00:00
// if they got a bad score or haven't stepped on the corresponding tap yet
2003-12-16 06:10:50 +00:00
const TapNoteScore tns = GetTapNoteScore ( hn . iTrack , hn . iStartRow );
2002-10-12 22:17:16 +00:00
const bool bSteppedOnTapNote = tns != TNS_NONE && tns != TNS_MISS ; // did they step on the start of this hold?
2002-10-12 22:04:44 +00:00
2003-12-16 06:10:50 +00:00
float fLife = GetHoldNoteLife ( hn );
2003-07-17 18:50:01 +00:00
// If the song beat is in the range of this hold:
2003-12-16 04:00:39 +00:00
if ( hn . iStartRow <= iSongRow && iSongRow <= hn . iEndRow )
2001-12-19 01:50:57 +00:00
{
2002-10-20 17:18:05 +00:00
bool bIsHoldingButton = INPUTMAPPER -> IsButtonDown ( GameI );
2003-04-10 05:46:31 +00:00
// TODO: Make the CPU miss sometimes.
2003-04-21 02:41:10 +00:00
if ( GAMESTATE -> m_PlayerController [ m_PlayerNumber ] != PC_HUMAN )
2002-10-20 17:18:05 +00:00
bIsHoldingButton = true ;
2002-04-16 17:31:00 +00:00
2003-07-15 23:24:48 +00:00
// set hold flag so NoteField can do intelligent drawing
2003-12-18 02:34:59 +00:00
m_pNoteField -> m_HeldHoldNotes [ hn ] = bIsHoldingButton && bSteppedOnTapNote ;
2004-01-01 01:53:25 +00:00
m_pNoteField -> m_ActiveHoldNotes [ hn ] = bSteppedOnTapNote ;
2002-06-14 22:25:22 +00:00
2004-05-07 04:57:29 +00:00
if ( bSteppedOnTapNote )
2003-08-18 04:20:51 +00:00
{
2004-05-07 04:57:29 +00:00
/* This hold note is not judged and we stepped on its head. Update
* iLastHeldRow. */
HoldNoteResult * hnr = m_pNoteField -> CreateHoldNoteResult ( hn );
hnr -> iLastHeldRow = min ( iSongRow , hn . iEndRow );
hnr = this -> CreateHoldNoteResult ( hn );
hnr -> iLastHeldRow = min ( iSongRow , hn . iEndRow );
2003-08-18 04:20:51 +00:00
}
2002-09-15 19:38:44 +00:00
2004-01-01 01:53:25 +00:00
2002-10-12 22:21:08 +00:00
if ( bSteppedOnTapNote && bIsHoldingButton )
2001-12-19 01:50:57 +00:00
{
2002-06-14 22:25:22 +00:00
// Increase life
2003-03-27 00:06:16 +00:00
fLife = 1 ;
2002-06-14 22:25:22 +00:00
2003-11-27 04:25:52 +00:00
m_pNoteField -> DidHoldNote ( hn . iTrack ); // update the "electric ghost" effect
2001-12-19 01:50:57 +00:00
}
2002-08-01 03:15:27 +00:00
else
2002-02-24 01:43:11 +00:00
{
2003-07-17 18:50:01 +00:00
/* What is this conditional for? It causes a problem: if a hold note
* begins on a freeze, you can tap it and then release it for the
* duration of the freeze; life doesn't count down until we're
* past the first beat. */
// if( fSongBeat-hn.fStartBeat > GAMESTATE->m_fCurBPS * GetMaxStepDistanceSeconds() )
// {
2002-06-14 22:25:22 +00:00
// Decrease life
2004-06-27 00:03:06 +00:00
fLife -= fDeltaTime / ADJUSTED_WINDOW ( OK );
2002-06-14 22:25:22 +00:00
fLife = max ( fLife , 0 ); // clamp
2003-07-17 18:50:01 +00:00
// }
2002-02-24 01:43:11 +00:00
}
2001-12-20 21:08:45 +00:00
}
2002-10-12 22:17:16 +00:00
/* check for NG. If the head was missed completely, don't count
* an NG. */
if ( bSteppedOnTapNote && fLife == 0 ) // the player has not pressed the button for a long time!
2002-06-14 22:25:22 +00:00
hns = HNS_NG ;
2001-12-19 01:50:57 +00:00
2002-02-24 01:43:11 +00:00
// check for OK
2003-12-16 04:00:39 +00:00
if ( iSongRow >= hn . iEndRow && bSteppedOnTapNote && fLife > 0 ) // if this HoldNote is in the past
2001-12-19 01:50:57 +00:00
{
2002-06-14 22:25:22 +00:00
fLife = 1 ;
hns = HNS_OK ;
2003-11-27 04:25:52 +00:00
m_pNoteField -> DidTapNote ( StyleI . col , TNS_PERFECT , true ); // bright ghost flash
2001-12-19 01:50:57 +00:00
}
2002-12-18 22:25:24 +00:00
if ( hns != HNS_NONE )
{
2003-04-10 05:46:31 +00:00
/* this note has been judged */
2003-03-16 17:45:32 +00:00
HandleHoldScore ( hns , tns );
2003-02-17 12:19:42 +00:00
m_HoldJudgment [ hn . iTrack ]. SetHoldJudgment ( hns );
2003-08-01 01:18:08 +00:00
int ms_error = ( hns == HNS_OK ) ? 0 : MAX_PRO_TIMING_ERROR ;
2003-12-23 00:26:00 +00:00
g_CurStageStats . iTotalError [ m_PlayerNumber ] += ms_error ;
2003-08-01 01:18:08 +00:00
if ( hns == HNS_NG ) /* don't show a 0 for an OK */
m_ProTimingDisplay . SetJudgment ( ms_error , TNS_MISS );
2002-12-18 22:25:24 +00:00
}
2003-12-16 06:10:50 +00:00
m_pNoteField -> SetHoldNoteLife ( hn , fLife ); // update the NoteField display
m_pNoteField -> SetHoldNoteScore ( hn , hns ); // update the NoteField display
2002-12-18 22:25:24 +00:00
2003-12-16 06:10:50 +00:00
SetHoldNoteLife ( hn , fLife );
SetHoldNoteScore ( hn , hns );
2001-12-19 01:50:57 +00:00
}
2003-06-10 19:53:33 +00:00
{
2004-02-25 05:49:54 +00:00
// Why was this originally "BeatToNoteRowNotRounded"? It should be rounded. -Chris
/* We want to send the crossed row message exactly when we cross the row--not
* .5 before the row. Use a very slow song (around 2 BPM) as a test case: without
* rounding, autoplay steps early. -glenn */
const int iRowNow = BeatToNoteRowNotRounded ( GAMESTATE -> m_fSongBeat );
if ( iRowNow >= 0 )
{
for ( ; m_iRowLastCrossed <= iRowNow ; m_iRowLastCrossed ++ ) // for each index we crossed since the last update
if ( GAMESTATE -> IsPlayerEnabled ( m_PlayerNumber ) )
CrossedRow ( m_iRowLastCrossed );
}
}
{
// TRICKY:
float fPositionSeconds = GAMESTATE -> m_fMusicSeconds ;
fPositionSeconds -= PREFSMAN -> m_fPadStickSeconds ;
const float fSongBeat = GAMESTATE -> m_pCurSong ? GAMESTATE -> m_pCurSong -> GetBeatFromElapsedTime ( fPositionSeconds ) : 0 ;
const int iRowNow = BeatToNoteRowNotRounded ( fSongBeat );
if ( iRowNow >= 0 )
{
for ( ; m_iMineRowLastCrossed <= iRowNow ; m_iMineRowLastCrossed ++ ) // for each index we crossed since the last update
if ( GAMESTATE -> IsPlayerEnabled ( m_PlayerNumber ) )
CrossedMineRow ( m_iMineRowLastCrossed );
}
2003-06-10 19:53:33 +00:00
}
2003-08-09 19:00:52 +00:00
2003-08-18 02:37:43 +00:00
// process transforms that are waiting to be applied
2003-12-23 02:17:28 +00:00
ApplyWaitingTransforms ();
/* Cache any newly-used note skins. Normally, the only new skins cached now are
* when we're adding course modifiers at the start of a song. If this is spending
* time loading skins in the middle of a song, something is wrong. */
m_pNoteField -> CacheAllUsedNoteSkins ();
ActorFrame :: Update ( fDeltaTime );
}
void PlayerMinus :: ApplyWaitingTransforms ()
{
2003-10-25 07:58:10 +00:00
for ( unsigned j = 0 ; j < GAMESTATE -> m_ModsToApply [ m_PlayerNumber ]. size (); j ++ )
2003-08-18 02:37:43 +00:00
{
2003-10-25 22:00:58 +00:00
const Attack & mod = GAMESTATE -> m_ModsToApply [ m_PlayerNumber ][ j ];
2003-10-25 07:58:10 +00:00
PlayerOptions po ;
/* Should this default to "" always? need it blank so we know if mod.sModifier
* changes the note skin. */
po . m_sNoteSkin = "" ;
po . FromString ( mod . sModifier );
2003-08-18 02:37:43 +00:00
2003-10-25 07:58:10 +00:00
float fStartBeat , fEndBeat ;
mod . GetAttackBeats ( GAMESTATE -> m_pCurSong , m_PlayerNumber , fStartBeat , fEndBeat );
2004-09-29 06:43:57 +00:00
fEndBeat = min ( fEndBeat , GetLastBeat () );
2003-10-25 07:58:10 +00:00
2003-10-27 22:26:34 +00:00
LOG -> Trace ( "Applying transform '%s' from %f to %f to '%s'" , mod . sModifier . c_str (), fStartBeat , fEndBeat ,
GAMESTATE -> m_pCurSong -> GetTranslitMainTitle (). c_str () );
2003-10-25 07:58:10 +00:00
if ( po . m_sNoteSkin != "" )
GAMESTATE -> SetNoteSkinForBeatRange ( m_PlayerNumber , po . m_sNoteSkin , fStartBeat , fEndBeat );
2004-06-28 07:26:00 +00:00
NoteDataUtil :: TransformNoteData ( * this , po , GAMESTATE -> GetCurrentStyle () -> m_StepsType , fStartBeat , fEndBeat );
2003-10-25 07:58:10 +00:00
m_pNoteField -> CopyRange ( this , BeatToNoteRow ( fStartBeat ), BeatToNoteRow ( fEndBeat ), BeatToNoteRow ( fStartBeat ) );
2003-08-18 02:37:43 +00:00
}
2003-10-25 07:58:10 +00:00
GAMESTATE -> m_ModsToApply [ m_PlayerNumber ]. clear ();
2001-11-03 10:52:42 +00:00
}
2003-08-24 17:04:21 +00:00
void PlayerMinus :: DrawPrimitives ()
2002-02-24 10:31:20 +00:00
{
2003-06-13 04:31:01 +00:00
// May have both players in doubles (for battle play); only draw primary player.
2004-06-28 07:26:00 +00:00
if ( GAMESTATE -> GetCurrentStyle () -> m_StyleType == Style :: ONE_PLAYER_TWO_CREDITS &&
2003-06-13 04:31:01 +00:00
m_PlayerNumber != GAMESTATE -> m_MasterPlayerNumber )
2003-05-05 06:48:20 +00:00
return ;
2003-04-14 23:09:35 +00:00
// Draw these below everything else.
m_ArrowBackdrop . Draw ();
2003-09-17 01:58:27 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind == 0 )
m_Combo . Draw ();
2002-07-31 19:40:40 +00:00
2003-11-26 08:25:45 +00:00
m_AttackDisplay . Draw ();
2003-11-25 06:45:06 +00:00
if ( TAP_JUDGMENTS_UNDER_FIELD )
2004-01-15 03:19:41 +00:00
DrawTapJudgments ();
2003-11-25 06:45:06 +00:00
if ( HOLD_JUDGMENTS_UNDER_FIELD )
2004-01-15 03:19:41 +00:00
DrawHoldJudgments ();
2003-11-25 06:45:06 +00:00
2003-04-01 19:31:27 +00:00
float fTilt = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. m_fPerspectiveTilt ;
2003-09-21 02:36:28 +00:00
float fSkew = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. m_fSkew ;
2003-08-17 00:15:54 +00:00
bool bReverse = GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. GetReversePercentForColumn ( 0 ) > 0.5 ;
2003-09-21 18:07:29 +00:00
2003-04-01 19:31:27 +00:00
2003-09-21 02:36:28 +00:00
DISPLAY -> CameraPushMatrix ();
2003-09-21 18:07:29 +00:00
DISPLAY -> PushMatrix ();
2002-02-24 10:31:20 +00:00
2003-09-21 18:07:29 +00:00
float fCenterY = ( GRAY_ARROWS_Y_STANDARD + GRAY_ARROWS_Y_REVERSE ) / 2 ;
2003-09-21 21:07:30 +00:00
// float fHeight = GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD;
2003-09-21 18:07:29 +00:00
DISPLAY -> LoadMenuPerspective ( 45 , SCALE ( fSkew , 0.f , 1.f , this -> GetX (), CENTER_X ), fCenterY );
2002-11-15 08:02:27 +00:00
2003-09-21 02:36:28 +00:00
float fOriginalY = m_pNoteField -> GetY ();
2002-02-24 10:31:20 +00:00
2003-09-21 18:07:29 +00:00
float fTiltDegrees = SCALE ( fTilt , - 1.f , + 1.f , + 30 , - 30 ) * ( bReverse ?- 1 : 1 );
float fZoom = SCALE ( GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. m_fEffects [ PlayerOptions :: EFFECT_MINI ], 0.f , 1.f , 1.f , 0.5f );
if ( fTilt > 0 )
fZoom *= SCALE ( fTilt , 0.f , 1.f , 1.f , 0.9f );
else
fZoom *= SCALE ( fTilt , 0.f , - 1.f , 1.f , 0.9f );
float fYOffset ;
if ( fTilt > 0 )
fYOffset = SCALE ( fTilt , 0.f , 1.f , 0.f , - 45.f ) * ( bReverse ?- 1 : 1 );
else
fYOffset = SCALE ( fTilt , 0.f , - 1.f , 0.f , - 20.f ) * ( bReverse ?- 1 : 1 );
m_pNoteField -> SetY ( fOriginalY + fYOffset );
m_pNoteField -> SetZoom ( fZoom );
m_pNoteField -> SetRotationX ( fTiltDegrees );
2003-08-24 17:04:21 +00:00
m_pNoteField -> Draw ();
2002-02-24 10:31:20 +00:00
2003-09-21 02:36:28 +00:00
m_pNoteField -> SetY ( fOriginalY );
DISPLAY -> CameraPopMatrix ();
2003-09-21 18:07:29 +00:00
DISPLAY -> PopMatrix ();
2002-02-24 10:31:20 +00:00
2003-11-25 06:45:06 +00:00
if ( ! TAP_JUDGMENTS_UNDER_FIELD )
DrawTapJudgments ();
2002-02-24 10:31:20 +00:00
2003-11-25 06:45:06 +00:00
if ( ! TAP_JUDGMENTS_UNDER_FIELD )
DrawHoldJudgments ();
}
void PlayerMinus :: DrawTapJudgments ()
{
2004-01-15 03:19:41 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind > 0 )
return ;
2003-11-25 06:45:06 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_bProTiming )
m_ProTimingDisplay . Draw ();
else
m_Judgment . Draw ();
}
void PlayerMinus :: DrawHoldJudgments ()
{
2004-01-15 03:19:41 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind > 0 )
return ;
2003-02-01 05:16:38 +00:00
for ( int c = 0 ; c < GetNumTracks (); c ++ )
2004-02-25 07:30:12 +00:00
{
g_NoteFieldMode [ m_PlayerNumber ]. BeginDrawTrack ( c );
2003-02-17 12:19:42 +00:00
m_HoldJudgment [ c ]. Draw ();
2004-02-25 07:30:12 +00:00
g_NoteFieldMode [ m_PlayerNumber ]. EndDrawTrack ( c );
}
2001-12-28 10:15:59 +00:00
}
2003-06-13 00:50:24 +00:00
/* It's OK for this function to search a little more than was requested. */
2004-02-11 05:57:51 +00:00
int PlayerMinus :: GetClosestNoteDirectional ( int col , float fBeat , float fMaxBeatsDistance , int iDirection ) const
2001-11-03 10:52:42 +00:00
{
2001-11-04 19:34:28 +00:00
// look for the closest matching step
2003-02-09 03:33:36 +00:00
const int iIndexStartLookingAt = BeatToNoteRow ( fBeat );
2003-02-09 02:07:30 +00:00
2003-06-13 00:50:24 +00:00
/* Number of elements to examine on either end of iIndexStartLookingAt. Make
* sure we always round up. */
const int iNumElementsToExamine = BeatToNoteRow ( fMaxBeatsDistance + 1 );
2003-02-09 03:33:36 +00:00
// Start at iIndexStartLookingAt and search outward.
for ( int delta = 0 ; delta < iNumElementsToExamine ; delta ++ )
2001-11-04 19:34:28 +00:00
{
2003-02-09 03:33:36 +00:00
int iCurrentIndex = iIndexStartLookingAt + ( iDirection * delta );
2001-11-03 10:52:42 +00:00
2003-02-09 03:33:36 +00:00
if ( iCurrentIndex < 0 ) continue ;
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( col , iCurrentIndex ). type == TapNote :: empty ) continue ; /* no note here */
2003-02-09 03:33:36 +00:00
if ( GetTapNoteScore ( col , iCurrentIndex ) != TNS_NONE ) continue ; /* this note has a score already */
2001-11-03 10:52:42 +00:00
2003-02-09 03:33:36 +00:00
return iCurrentIndex ;
2001-11-04 19:34:28 +00:00
}
2002-05-29 09:47:24 +00:00
2003-02-09 03:33:36 +00:00
return - 1 ;
}
2004-02-11 05:57:51 +00:00
int PlayerMinus :: GetClosestNote ( int col , float fBeat , float fMaxBeatsAhead , float fMaxBeatsBehind ) const
2003-02-09 03:33:36 +00:00
{
2003-02-17 05:25:20 +00:00
int Fwd = GetClosestNoteDirectional ( col , fBeat , fMaxBeatsAhead , 1 );
int Back = GetClosestNoteDirectional ( col , fBeat , fMaxBeatsBehind , - 1 );
2003-02-09 03:33:36 +00:00
if ( Fwd == - 1 && Back == - 1 ) return - 1 ;
if ( Fwd == - 1 ) return Back ;
if ( Back == - 1 ) return Fwd ;
/* Figure out which row is closer. */
const float DistToFwd = fabsf ( fBeat - NoteRowToBeat ( Fwd ));
const float DistToBack = fabsf ( fBeat - NoteRowToBeat ( Back ));
if ( DistToFwd > DistToBack ) return Back ;
return Fwd ;
2003-02-09 02:07:30 +00:00
}
2003-07-13 00:34:30 +00:00
2003-08-24 17:04:21 +00:00
void PlayerMinus :: Step ( int col , RageTimer tm )
2003-02-09 02:07:30 +00:00
{
2003-12-23 00:26:00 +00:00
if ( GAMESTATE -> m_SongOptions . m_LifeType == SongOptions :: LIFE_BATTERY && g_CurStageStats . bFailed [ m_PlayerNumber ] ) // Oni dead
2003-02-09 02:07:30 +00:00
return ; // do nothing
2003-08-24 17:04:21 +00:00
//LOG->Trace( "PlayerMinus::HandlePlayerStep()" );
2003-02-09 02:07:30 +00:00
ASSERT ( col >= 0 && col <= GetNumTracks () );
2004-02-25 05:49:54 +00:00
float fPositionSeconds = GAMESTATE -> m_fMusicSeconds ;
fPositionSeconds -= tm . Ago ();
const float fSongBeat = GAMESTATE -> m_pCurSong ? GAMESTATE -> m_pCurSong -> GetBeatFromElapsedTime ( fPositionSeconds ) : GAMESTATE -> m_fSongBeat ;
2003-11-15 23:19:13 +00:00
//
// Check for step on a TapNote
//
2004-02-25 05:49:54 +00:00
int iIndexOverlappingNote = GetClosestNote ( col , fSongBeat ,
2003-03-26 22:22:10 +00:00
StepSearchDistanceForwards * GAMESTATE -> m_fCurBPS * GAMESTATE -> m_SongOptions . m_fMusicRate ,
StepSearchDistanceBackwards * GAMESTATE -> m_fCurBPS * GAMESTATE -> m_SongOptions . m_fMusicRate );
2003-02-09 02:07:30 +00:00
//LOG->Trace( "iIndexStartLookingAt = %d, iNumElementsToExamine = %d", iIndexStartLookingAt, iNumElementsToExamine );
2004-07-02 01:24:11 +00:00
// calculate TapNoteScore
TapNoteScore score = TNS_NONE ;
2002-05-29 09:47:24 +00:00
if ( iIndexOverlappingNote != - 1 )
{
2002-06-24 22:04:31 +00:00
// compute the score for this hit
2003-07-13 00:34:30 +00:00
const float fStepBeat = NoteRowToBeat ( ( float ) iIndexOverlappingNote );
const float fStepSeconds = GAMESTATE -> m_pCurSong -> GetElapsedTimeFromBeat ( fStepBeat );
/* We actually stepped on the note this long ago: */
2004-01-18 06:53:25 +00:00
const float fTimeSinceStep = tm . Ago ();
/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
* out what the music time is as of now. */
2004-02-27 03:32:11 +00:00
const float fCurrentMusicSeconds = GAMESTATE -> m_fMusicSeconds + ( GAMESTATE -> m_LastBeatUpdate . Ago () * GAMESTATE -> m_SongOptions . m_fMusicRate );
2004-01-18 06:53:25 +00:00
2003-07-13 00:34:30 +00:00
/* ... which means it happened at this point in the music: */
2004-02-27 03:32:11 +00:00
const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE -> m_SongOptions . m_fMusicRate ;
2003-02-17 05:25:20 +00:00
2003-02-09 03:33:36 +00:00
// The offset from the actual step in seconds:
2003-07-22 05:03:38 +00:00
const float fNoteOffset = ( fStepSeconds - fMusicSeconds ) / GAMESTATE -> m_SongOptions . m_fMusicRate ; // account for music rate
2004-01-18 06:53:25 +00:00
// LOG->Trace("step was %.3f ago, music is off by %f: %f vs %f, step was %f off",
// fTimeSinceStep, GAMESTATE->m_LastBeatUpdate.Ago()/GAMESTATE->m_SongOptions.m_fMusicRate,
// fStepSeconds, fMusicSeconds, fNoteOffset );
2002-10-01 01:53:56 +00:00
2003-07-22 05:03:38 +00:00
const float fSecondsFromPerfect = fabsf ( fNoteOffset );
2002-06-24 22:04:31 +00:00
2003-08-10 21:27:54 +00:00
TapNote tn = GetTapNote ( col , iIndexOverlappingNote );
2003-04-21 02:41:10 +00:00
switch ( GAMESTATE -> m_PlayerController [ m_PlayerNumber ] )
2003-04-07 21:24:14 +00:00
{
2004-09-12 05:56:24 +00:00
case PC_HUMAN :
2003-08-10 21:27:54 +00:00
2004-09-12 05:56:24 +00:00
switch ( tn . type )
2003-11-10 01:03:47 +00:00
{
2004-09-12 05:56:24 +00:00
case TapNote :: mine :
2003-11-10 01:03:47 +00:00
// stepped too close to mine?
2004-06-27 00:03:06 +00:00
if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Mine ) )
2003-11-10 01:03:47 +00:00
{
2003-11-12 05:21:51 +00:00
m_soundMine . Play ();
2004-01-02 08:43:14 +00:00
score = TNS_HIT_MINE ;
2003-11-11 07:36:28 +00:00
if ( m_pLifeMeter )
m_pLifeMeter -> ChangeLifeMine ();
if ( m_pCombinedLifeMeter )
m_pCombinedLifeMeter -> ChangeLifeMine ( m_PlayerNumber );
2003-11-17 05:26:29 +00:00
m_pNoteField -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
2004-01-02 08:43:14 +00:00
m_pNoteField -> DidTapNote ( col , score , false );
2003-11-10 01:03:47 +00:00
}
2003-11-15 23:19:13 +00:00
break ;
2004-09-12 05:56:24 +00:00
case TapNote :: attack :
score = TNS_NONE ; // don't score this as anything
if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Attack ) )
2003-11-10 01:03:47 +00:00
{
2004-09-12 05:56:24 +00:00
m_soundAttackLaunch . Play ();
// put attack in effect
Attack attack = this -> GetAttackAt ( col , iIndexOverlappingNote );
GAMESTATE -> LaunchAttack ( OPPOSITE_PLAYER [ m_PlayerNumber ], attack );
2003-11-17 07:14:41 +00:00
2004-09-12 05:56:24 +00:00
// remove all TapAttacks on this row
for ( int t = 0 ; t < this -> GetNumTracks (); t ++ )
{
TapNote tn = this -> GetTapNote ( t , iIndexOverlappingNote );
if ( tn . type == TapNote :: attack )
2003-11-17 07:14:41 +00:00
{
2004-09-12 05:56:24 +00:00
this -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
m_pNoteField -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
2003-11-17 07:14:41 +00:00
}
2003-11-17 05:26:29 +00:00
}
2003-11-10 01:03:47 +00:00
}
2004-09-12 05:56:24 +00:00
break ;
default :
if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Marvelous ) ) score = TNS_MARVELOUS ;
else if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Perfect ) ) score = TNS_PERFECT ;
else if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Great ) ) score = TNS_GREAT ;
else if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Good ) ) score = TNS_GOOD ;
else if ( fSecondsFromPerfect <= ADJUSTED_WINDOW ( Boo ) ) score = TNS_BOO ;
else score = TNS_NONE ;
2003-11-15 23:19:13 +00:00
break ;
2003-11-10 01:03:47 +00:00
}
2003-04-21 02:41:10 +00:00
break ;
2004-09-12 05:56:24 +00:00
2003-04-21 02:41:10 +00:00
case PC_CPU :
2004-01-12 01:48:50 +00:00
case PC_AUTOPLAY :
switch ( GAMESTATE -> m_PlayerController [ m_PlayerNumber ] )
{
case PC_CPU :
2004-01-12 03:47:55 +00:00
score = PlayerAI :: GetTapNoteScore ( m_PlayerNumber );
2004-01-12 01:48:50 +00:00
break ;
case PC_AUTOPLAY :
score = TNS_MARVELOUS ;
break ;
}
2003-09-08 15:30:26 +00:00
2004-01-11 22:29:18 +00:00
// TRICKY: We're asking the AI to judge mines. consider TNS_GOOD and below
// as "mine was hit" and everything else as "mine was avoided"
2004-09-12 05:56:24 +00:00
if ( tn . type == TapNote :: mine )
2004-01-11 22:29:18 +00:00
{
2004-01-11 23:31:37 +00:00
// The CPU hits a lot of mines. Only consider hitting the
// first mine for a row. We know we're the first mine if
// there are are no mines to the left of us.
for ( int t = 0 ; t < col ; t ++ )
{
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( t , iIndexOverlappingNote ). type == TapNote :: mine ) // there's a mine to the left of us
2004-01-11 23:31:37 +00:00
return ; // avoid
}
// The CPU hits a lot of mines. Make it less likely to hit
// mines that don't have a tap note on the same row.
bool bTapsOnRow = IsThereATapOrHoldHeadAtRow ( iIndexOverlappingNote );
TapNoteScore get_to_avoid = bTapsOnRow ? TNS_GREAT : TNS_GOOD ;
if ( score >= get_to_avoid )
2004-01-11 22:29:18 +00:00
{
return ; // avoided
}
else
{
score = TNS_HIT_MINE ;
m_soundMine . Play ();
if ( m_pLifeMeter )
m_pLifeMeter -> ChangeLifeMine ();
if ( m_pCombinedLifeMeter )
m_pCombinedLifeMeter -> ChangeLifeMine ( m_PlayerNumber );
m_pNoteField -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
m_pNoteField -> DidTapNote ( col , score , false );
}
}
2003-04-10 05:46:31 +00:00
/* AI will generate misses here. Don't handle a miss like a regular note because
* we want the judgment animation to appear delayed. Instead, return early if
2004-05-24 04:26:54 +00:00
* AI generated a miss, and let UpdateMissedTapNotesOlderThan() detect and handle the
2003-04-10 05:46:31 +00:00
* misses. */
if ( score == TNS_MISS )
return ;
2003-08-10 21:27:54 +00:00
2004-01-11 22:29:18 +00:00
2004-09-12 05:56:24 +00:00
if ( tn . type == TapNote :: attack && score > TNS_GOOD )
2003-11-17 05:26:29 +00:00
{
2003-11-20 06:50:05 +00:00
m_soundAttackLaunch . Play ();
2003-11-17 05:26:29 +00:00
score = TNS_NONE ; // don't score this as anything
2003-11-17 07:14:41 +00:00
// put attack in effect
Attack attack = this -> GetAttackAt ( col , iIndexOverlappingNote );
GAMESTATE -> LaunchAttack ( OPPOSITE_PLAYER [ m_PlayerNumber ], attack );
// remove all TapAttacks on this row
for ( int t = 0 ; t < this -> GetNumTracks (); t ++ )
{
TapNote tn = this -> GetTapNote ( t , iIndexOverlappingNote );
2004-09-12 05:56:24 +00:00
if ( tn . type == TapNote :: attack )
2003-11-17 07:14:41 +00:00
{
this -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
m_pNoteField -> SetTapNote ( col , iIndexOverlappingNote , TAP_EMPTY ); // remove from NoteField
}
}
2003-11-17 05:26:29 +00:00
}
2003-04-21 02:41:10 +00:00
break ;
2003-11-17 07:14:41 +00:00
2003-04-21 02:41:10 +00:00
default :
ASSERT ( 0 );
2003-04-22 04:54:04 +00:00
score = TNS_NONE ;
break ;
2003-04-07 21:24:14 +00:00
}
2003-11-10 23:25:44 +00:00
2003-08-10 19:07:54 +00:00
2004-06-27 01:16:00 +00:00
// Do game-specific score mapping.
2004-07-25 17:07:32 +00:00
const Game * pGame = GAMESTATE -> GetCurrentGame ();
if ( score == TNS_MARVELOUS ) score = pGame -> m_mapMarvelousTo ;
if ( score == TNS_PERFECT ) score = pGame -> m_mapPerfectTo ;
if ( score == TNS_GREAT ) score = pGame -> m_mapGreatTo ;
if ( score == TNS_GOOD ) score = pGame -> m_mapGoodTo ;
if ( score == TNS_BOO ) score = pGame -> m_mapBooTo ;
2004-06-27 01:16:00 +00:00
2003-08-10 19:07:54 +00:00
if ( score != TNS_NONE && score != TNS_MISS )
2003-08-01 00:12:10 +00:00
{
int ms_error = ( int ) roundf ( fSecondsFromPerfect * 1000 );
ms_error = min ( ms_error , MAX_PRO_TIMING_ERROR );
2003-12-23 00:26:00 +00:00
g_CurStageStats . iTotalError [ m_PlayerNumber ] += ms_error ;
2003-09-14 14:05:15 +00:00
if ( ! GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind )
m_ProTimingDisplay . SetJudgment ( ms_error , score );
2003-08-01 00:12:10 +00:00
}
2003-07-25 08:05:29 +00:00
if ( score == TNS_MARVELOUS && ! GAMESTATE -> ShowMarvelous ())
2002-08-27 16:53:25 +00:00
score = TNS_PERFECT ;
2004-03-28 09:49:05 +00:00
LOG -> Trace ( "XXX: %i col %i, at %f, music at %f, step was at %f, off by %f" ,
score , col , fStepSeconds , fCurrentMusicSeconds , fMusicSeconds , fNoteOffset );
2003-09-03 08:01:48 +00:00
// LOG->Trace("Note offset: %f (fSecondsFromPerfect = %f), Score: %i", fNoteOffset, fSecondsFromPerfect, score);
2003-04-11 00:12:07 +00:00
2002-12-18 22:25:24 +00:00
SetTapNoteScore ( col , iIndexOverlappingNote , score );
2003-04-11 00:12:07 +00:00
if ( score != TNS_NONE )
SetTapNoteOffset ( col , iIndexOverlappingNote , - fNoteOffset );
2002-12-17 22:07:59 +00:00
2004-04-15 05:41:57 +00:00
if ( GAMESTATE -> m_PlayerController [ m_PlayerNumber ] == PC_HUMAN &&
score >= TNS_GREAT )
HandleAutosync ( fNoteOffset );
2004-08-10 06:39:25 +00:00
//Keep this here so we get the same data as Autosync
NSMAN -> ReportTiming ( fNoteOffset , m_PlayerNumber );
2004-01-02 08:43:14 +00:00
if ( m_pPrimaryScoreKeeper )
m_pPrimaryScoreKeeper -> HandleTapScore ( score );
if ( m_pSecondaryScoreKeeper )
m_pSecondaryScoreKeeper -> HandleTapScore ( score );
2003-11-11 07:36:28 +00:00
2004-09-12 05:56:24 +00:00
switch ( tn . type )
2003-11-11 07:36:28 +00:00
{
2004-09-12 05:56:24 +00:00
case TapNote :: tap :
case TapNote :: hold_head :
2004-01-24 19:11:52 +00:00
// don't the row if this note is a mine or tap attack
2003-11-11 07:36:28 +00:00
if ( IsRowCompletelyJudged ( iIndexOverlappingNote ) )
OnRowCompletelyJudged ( iIndexOverlappingNote );
}
2003-11-10 23:25:44 +00:00
if ( score == TNS_MISS || score == TNS_BOO )
{
m_iDCState = AS2D_MISS ;
}
if ( score == TNS_GOOD || score == TNS_GREAT )
{
m_iDCState = AS2D_GOOD ;
}
if ( score == TNS_PERFECT || score == TNS_MARVELOUS )
{
m_iDCState = AS2D_GREAT ;
2003-11-17 05:26:29 +00:00
if ( m_pLifeMeter && m_pLifeMeter -> GetLife () == 1.0f ) // full life
2003-11-10 23:25:44 +00:00
{
m_iDCState = AS2D_FEVER ; // super celebrate time :)
}
}
2004-07-02 20:08:17 +00:00
m_pNoteField -> Step ( col , score );
2002-05-29 09:47:24 +00:00
}
2004-07-02 20:08:17 +00:00
else
m_pNoteField -> Step ( col , TNS_NONE );
2001-11-04 19:34:28 +00:00
}
2001-11-03 10:52:42 +00:00
2004-04-15 05:41:57 +00:00
void PlayerMinus :: HandleAutosync ( float fNoteOffset )
{
if ( ! GAMESTATE -> m_SongOptions . m_bAutoSync )
return ;
m_fOffset [ m_iOffsetSample ++ ] = fNoteOffset ;
if ( m_iOffsetSample < SAMPLE_COUNT )
return ; /* need more */
const float mean = calc_mean ( m_fOffset , m_fOffset + SAMPLE_COUNT );
const float stddev = calc_stddev ( m_fOffset , m_fOffset + SAMPLE_COUNT );
if ( stddev < .03 && stddev < fabsf ( mean )) { //If they stepped with less than .03 error
GAMESTATE -> m_pCurSong -> m_Timing . m_fBeat0OffsetInSeconds += mean ;
LOG -> Trace ( "Offset corrected by %f. Error in steps: %f seconds." , mean , stddev );
} else
LOG -> Trace ( "Offset NOT corrected. Average offset: %f seconds. Error: %f seconds." , mean , stddev );
m_iOffsetSample = 0 ;
}
2003-03-26 19:34:59 +00:00
2003-08-24 17:04:21 +00:00
void PlayerMinus :: OnRowCompletelyJudged ( int iIndexThatWasSteppedOn )
2001-11-04 19:34:28 +00:00
{
2003-09-03 08:01:48 +00:00
// LOG->Trace( "PlayerMinus::OnRowCompletelyJudged" );
2002-06-24 22:04:31 +00:00
2003-03-26 19:34:59 +00:00
/* Find the minimum score of the row. This will never be TNS_NONE, since this
* function is only called when a row is completed. */
2003-08-10 19:07:54 +00:00
/* Instead, use the last tap score (ala DDR). Using the minimum results in
* slightly more harsh scoring than DDR */
2003-09-19 20:07:58 +00:00
/* I'm not sure this is right, either. Can you really jump a boo and a perfect
* and get scored for a perfect? (That's so loose, you can gallop jumps.) -glenn */
2004-01-02 08:43:14 +00:00
/* Instead of grading individual columns, DDR sets a "was pressed recently"
* countdown every time you step on a column. When you step on the first note of
* the jump, it sets the first "was pressed recently" timer. Then, when you do
* the 2nd step of the jump, it sets another column's timer then AND's the jump
* columns with the "was pressed recently" columns to see whether or not you hit
* all the columns of the jump. -Chris */
2003-03-26 19:34:59 +00:00
// TapNoteScore score = MinTapNoteScore(iIndexThatWasSteppedOn);
TapNoteScore score = LastTapNoteScore ( iIndexThatWasSteppedOn );
2003-03-26 22:22:10 +00:00
ASSERT ( score != TNS_NONE );
2004-01-02 08:43:14 +00:00
ASSERT ( score != TNS_HIT_MINE );
2001-11-03 10:52:42 +00:00
2002-10-24 05:24:56 +00:00
/* If the whole row was hit with perfects or greats, remove the row
* from the NoteField, so it disappears. */
2002-02-24 01:43:11 +00:00
2003-02-01 05:16:38 +00:00
for ( int c = 0 ; c < GetNumTracks (); c ++ ) // for each column
2002-02-24 01:43:11 +00:00
{
2003-08-10 19:07:54 +00:00
TapNote tn = GetTapNote ( c , iIndexThatWasSteppedOn );
2004-09-12 05:56:24 +00:00
if ( tn . type == TapNote :: empty ) continue ; /* no note in this col */
if ( tn . type == TapNote :: mine ) continue ; /* don't flash on mines b/c they're supposed to be missed */
2003-08-10 19:07:54 +00:00
// If the score is great or better, remove the note from the screen to
2003-09-14 14:05:15 +00:00
// indicate success. (Or always if blind is on.)
2003-09-17 01:50:54 +00:00
if ( score >= TNS_GREAT || GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind )
2003-08-24 17:04:21 +00:00
m_pNoteField -> SetTapNote ( c , iIndexThatWasSteppedOn , TAP_EMPTY );
2002-10-24 05:24:56 +00:00
2003-03-26 17:38:19 +00:00
// show the ghost arrow for this column
2003-09-14 14:05:15 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fBlind )
2003-11-27 04:25:52 +00:00
m_pNoteField -> DidTapNote ( c , TNS_MARVELOUS , false );
2003-09-14 14:05:15 +00:00
else
2003-03-26 17:38:19 +00:00
{
2003-09-14 14:05:15 +00:00
switch ( score )
2003-03-16 17:45:32 +00:00
{
2003-09-14 14:05:15 +00:00
case TNS_GREAT :
case TNS_PERFECT :
case TNS_MARVELOUS :
{
2003-12-23 00:26:00 +00:00
bool bBright = g_CurStageStats . iCurCombo [ m_PlayerNumber ] > ( int ) BRIGHT_GHOST_COMBO_THRESHOLD ;
2003-11-27 04:25:52 +00:00
m_pNoteField -> DidTapNote ( c , score , bBright );
2003-09-14 14:05:15 +00:00
}
break ;
2003-03-16 17:45:32 +00:00
}
2002-10-02 05:42:59 +00:00
}
2002-10-24 05:24:56 +00:00
}
2002-10-02 05:42:59 +00:00
2003-03-26 21:35:15 +00:00
HandleTapRowScore ( iIndexThatWasSteppedOn ); // update score
2002-05-19 01:59:48 +00:00
2003-09-17 01:58:27 +00:00
m_Judgment . SetJudgment ( score );
2001-11-03 10:52:42 +00:00
}
2004-05-24 04:26:54 +00:00
void PlayerMinus :: UpdateTapNotesMissedOlderThan ( float fMissIfOlderThanSeconds )
2001-11-04 19:34:28 +00:00
{
2004-05-24 04:26:54 +00:00
//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
2003-09-19 20:07:58 +00:00
int iMissIfOlderThanThisIndex ;
2003-06-13 00:24:33 +00:00
{
2003-09-19 20:07:58 +00:00
const float fEarliestTime = GAMESTATE -> m_fMusicSeconds - fMissIfOlderThanSeconds ;
bool bFreeze ;
float fMissIfOlderThanThisBeat ;
float fThrowAway ;
GAMESTATE -> m_pCurSong -> GetBeatAndBPSFromElapsedTime ( fEarliestTime , fMissIfOlderThanThisBeat , fThrowAway , bFreeze );
iMissIfOlderThanThisIndex = BeatToNoteRow ( fMissIfOlderThanThisBeat );
if ( bFreeze )
{
2004-01-24 19:11:52 +00:00
/* If there is a freeze on iMissIfOlderThanThisIndex, include this index too.
* Otherwise we won't show misses for tap notes on freezes until the
2003-09-19 20:07:58 +00:00
* freeze finishes. */
iMissIfOlderThanThisIndex ++ ;
}
2003-06-13 00:24:33 +00:00
}
2001-11-03 10:52:42 +00:00
2002-04-16 17:31:00 +00:00
// Since this is being called every frame, let's not check the whole array every time.
2001-11-04 19:34:28 +00:00
// Instead, only check 10 elements back. Even 10 is overkill.
2003-09-19 20:07:58 +00:00
const int iStartCheckingAt = max ( 0 , iMissIfOlderThanThisIndex - 10 );
2001-11-03 10:52:42 +00:00
2002-07-31 19:40:40 +00:00
//LOG->Trace( "iStartCheckingAt: %d iMissIfOlderThanThisIndex: %d", iStartCheckingAt, iMissIfOlderThanThisIndex );
2003-02-09 04:05:31 +00:00
int iNumMissesFound = 0 ;
2003-06-13 00:24:33 +00:00
for ( int r = iStartCheckingAt ; r < iMissIfOlderThanThisIndex ; r ++ )
2001-11-03 10:52:42 +00:00
{
2003-03-26 21:35:15 +00:00
bool MissedNoteOnThisRow = false ;
2003-02-01 05:16:38 +00:00
for ( int t = 0 ; t < GetNumTracks (); t ++ )
2001-11-03 10:52:42 +00:00
{
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( t , r ). type == TapNote :: empty ) /* no note here */
continue ;
if ( GetTapNoteScore ( t , r ) != TNS_NONE ) /* note here is already hit */
continue ;
2003-08-10 19:07:54 +00:00
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( t , r ). type != TapNote :: mine )
2003-08-10 19:07:54 +00:00
{
// A normal note. Penalize for not stepping on it.
MissedNoteOnThisRow = true ;
SetTapNoteScore ( t , r , TNS_MISS );
2003-12-23 00:26:00 +00:00
g_CurStageStats . iTotalError [ m_PlayerNumber ] += MAX_PRO_TIMING_ERROR ;
2003-08-10 19:07:54 +00:00
m_ProTimingDisplay . SetJudgment ( MAX_PRO_TIMING_ERROR , TNS_MISS );
}
2002-10-02 05:42:59 +00:00
}
2003-03-26 21:35:15 +00:00
2003-08-10 19:07:54 +00:00
if ( MissedNoteOnThisRow )
{
iNumMissesFound ++ ;
HandleTapRowScore ( r );
}
2001-11-03 10:52:42 +00:00
}
2002-05-29 09:47:24 +00:00
if ( iNumMissesFound > 0 )
2003-02-17 12:19:42 +00:00
m_Judgment . SetJudgment ( TNS_MISS );
2001-11-03 10:52:42 +00:00
}
2003-08-24 17:04:21 +00:00
void PlayerMinus :: CrossedRow ( int iNoteRow )
2002-06-24 22:04:31 +00:00
{
2003-06-10 19:53:33 +00:00
// If we're doing random vanish, randomise notes on the fly.
if ( GAMESTATE -> m_CurrentPlayerOptions [ m_PlayerNumber ]. m_fAppearances [ PlayerOptions :: APPEARANCE_RANDOMVANISH ] == 1 )
RandomiseNotes ( iNoteRow );
2003-04-07 21:24:14 +00:00
// check to see if there's at the crossed row
2003-07-13 00:34:30 +00:00
RageTimer now ;
if ( GAMESTATE -> m_PlayerController [ m_PlayerNumber ] != PC_HUMAN )
{
for ( int t = 0 ; t < GetNumTracks (); t ++ )
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( t , iNoteRow ). type != TapNote :: empty )
2003-08-09 18:42:21 +00:00
if ( GetTapNoteScore ( t , iNoteRow ) == TNS_NONE )
Step ( t , now );
2003-07-13 00:34:30 +00:00
}
2004-02-25 05:49:54 +00:00
}
2003-08-31 22:39:05 +00:00
2004-02-25 05:49:54 +00:00
void PlayerMinus :: CrossedMineRow ( int iNoteRow )
{
2003-08-31 22:39:05 +00:00
// Hold the panel while crossing a mine will cause the mine to explode
2004-02-25 05:49:54 +00:00
RageTimer now ;
2003-08-31 22:39:05 +00:00
for ( int t = 0 ; t < GetNumTracks (); t ++ )
{
2004-09-12 05:56:24 +00:00
if ( GetTapNote ( t , iNoteRow ). type == TapNote :: mine )
2003-08-31 22:39:05 +00:00
{
const StyleInput StyleI ( m_PlayerNumber , t );
2004-06-28 07:26:00 +00:00
const GameInput GameI = GAMESTATE -> GetCurrentStyle () -> StyleInputToGameInput ( StyleI );
2004-02-26 07:27:54 +00:00
if ( PREFSMAN -> m_fPadStickSeconds > 0 )
{
float fSecsHeld = INPUTMAPPER -> GetSecsHeld ( GameI );
if ( fSecsHeld >= PREFSMAN -> m_fPadStickSeconds )
Step ( t , now + ( - PREFSMAN -> m_fPadStickSeconds ) );
}
else
{
bool bIsDown = INPUTMAPPER -> IsButtonDown ( GameI );
if ( bIsDown )
Step ( t , now );
}
2003-08-31 22:39:05 +00:00
}
}
2002-06-24 22:04:31 +00:00
}
2001-11-03 10:52:42 +00:00
2003-08-24 17:04:21 +00:00
void PlayerMinus :: RandomiseNotes ( int iNoteRow )
2003-05-04 13:58:45 +00:00
{
2003-10-04 04:35:22 +00:00
const int NewNoteRow = ( int )( iNoteRow + 50 / GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_fScrollSpeed ); // change the row to look ahead from based upon their speed mod
2003-05-04 13:58:45 +00:00
2003-10-25 03:17:39 +00:00
bool UpdateNoteField = false ;
2003-05-04 13:58:45 +00:00
int iNumOfTracks = GetNumTracks ();
2003-10-24 22:57:47 +00:00
for ( int t = 0 ; t + 1 < iNumOfTracks ; t ++ )
2003-05-04 13:58:45 +00:00
{
2003-10-24 22:57:47 +00:00
int iRandomTrackToSwapWith = RandomInt ( 0 , iNumOfTracks - 1 );
const TapNote t1 = GetTapNote ( t , NewNoteRow );
const TapNote t2 = GetTapNote ( iRandomTrackToSwapWith , NewNoteRow );
2004-09-12 05:56:24 +00:00
if ( ( t1 . type == TapNote :: tap || t1 . type == TapNote :: empty ) &&
( t2 . type == TapNote :: tap || t2 . type == TapNote :: empty ) )
2003-05-04 13:58:45 +00:00
{
2003-10-24 22:57:47 +00:00
SetTapNote ( t , NewNoteRow , t2 );
SetTapNote ( iRandomTrackToSwapWith , NewNoteRow , t1 );
2003-10-25 03:17:39 +00:00
UpdateNoteField = true ;
2003-05-04 13:58:45 +00:00
}
}
2003-10-25 03:17:39 +00:00
if ( UpdateNoteField )
m_pNoteField -> CopyRange ( this , NewNoteRow , NewNoteRow , NewNoteRow );
2003-05-04 13:58:45 +00:00
}
2001-11-04 19:34:28 +00:00
2003-08-24 17:04:21 +00:00
void PlayerMinus :: HandleTapRowScore ( unsigned row )
2002-06-24 22:04:31 +00:00
{
2003-03-26 21:35:15 +00:00
TapNoteScore scoreOfLastTap = LastTapNoteScore ( row );
2004-01-02 08:43:14 +00:00
int iNumTapsInRow = this -> GetNumTracksWithTapOrHoldHead ( row );
2003-03-26 21:35:15 +00:00
ASSERT ( iNumTapsInRow > 0 );
2002-10-02 05:42:59 +00:00
2003-04-21 04:11:01 +00:00
bool NoCheating = true ;
2003-04-22 04:54:04 +00:00
#ifdef DEBUG
NoCheating = false ;
#endif
2002-08-23 20:18:29 +00:00
2003-04-21 04:11:01 +00:00
if ( GAMESTATE -> m_bDemonstrationOrJukebox )
NoCheating = false ;
// don't accumulate points if AutoPlay is on.
2003-04-22 04:54:04 +00:00
if ( NoCheating && GAMESTATE -> m_PlayerController [ m_PlayerNumber ] == PC_AUTOPLAY )
2003-04-21 04:11:01 +00:00
return ;
2003-09-07 01:01:20 +00:00
/* Update miss combo, and handle "combo stopped" messages. */
2003-12-23 00:26:00 +00:00
int & iCurCombo = g_CurStageStats . iCurCombo [ m_PlayerNumber ];
2003-09-06 02:20:09 +00:00
switch ( scoreOfLastTap )
{
2003-09-06 02:26:06 +00:00
case TNS_MARVELOUS :
case TNS_PERFECT :
case TNS_GREAT :
2003-12-23 00:26:00 +00:00
g_CurStageStats . iCurMissCombo [ m_PlayerNumber ] = 0 ;
2003-09-06 03:25:45 +00:00
SCREENMAN -> PostMessageToTopScreen ( SM_MissComboAborted , 0 );
2003-09-06 02:26:06 +00:00
break ;
2003-09-06 03:25:45 +00:00
2003-09-06 02:26:06 +00:00
case TNS_MISS :
2003-12-23 00:26:00 +00:00
++ g_CurStageStats . iCurMissCombo [ m_PlayerNumber ];
2003-11-10 23:25:44 +00:00
m_iDCState = AS2D_MISS ; // update dancing 2d characters that may have missed a note
2003-09-06 02:20:09 +00:00
case TNS_GOOD :
case TNS_BOO :
if ( iCurCombo > 50 )
SCREENMAN -> PostMessageToTopScreen ( SM_ComboStopped , 0 );
iCurCombo = 0 ;
break ;
2004-01-02 08:43:14 +00:00
default :
ASSERT ( 0 );
2003-09-06 02:20:09 +00:00
}
2003-09-07 01:01:20 +00:00
/* The score keeper updates the hit combo. Remember the old combo for handling announcers. */
2003-12-23 00:26:00 +00:00
const int iOldCombo = g_CurStageStats . iCurCombo [ m_PlayerNumber ];
2003-09-07 01:01:20 +00:00
if ( m_pPrimaryScoreKeeper )
2004-01-02 08:43:14 +00:00
m_pPrimaryScoreKeeper -> HandleTapRowScore ( scoreOfLastTap , iNumTapsInRow );
2003-09-07 01:01:20 +00:00
if ( m_pSecondaryScoreKeeper )
2004-01-02 08:43:14 +00:00
m_pSecondaryScoreKeeper -> HandleTapRowScore ( scoreOfLastTap , iNumTapsInRow );
2003-09-07 01:01:20 +00:00
2004-07-31 21:26:11 +00:00
m_Combo . SetCombo ( g_CurStageStats . iCurCombo [ m_PlayerNumber ], g_CurStageStats . iCurMissCombo [ m_PlayerNumber ] );
2003-09-19 20:15:15 +00:00
2003-09-07 01:01:20 +00:00
#define CROSSED( x ) (iOldCombo<x && iCurCombo>=x)
if ( CROSSED ( 100 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_100Combo , 0 );
else if ( CROSSED ( 200 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_200Combo , 0 );
else if ( CROSSED ( 300 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_300Combo , 0 );
else if ( CROSSED ( 400 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_400Combo , 0 );
else if ( CROSSED ( 500 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_500Combo , 0 );
else if ( CROSSED ( 600 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_600Combo , 0 );
else if ( CROSSED ( 700 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_700Combo , 0 );
else if ( CROSSED ( 800 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_800Combo , 0 );
else if ( CROSSED ( 900 ) )
SCREENMAN -> PostMessageToTopScreen ( SM_900Combo , 0 );
else if ( CROSSED ( 1000 ))
SCREENMAN -> PostMessageToTopScreen ( SM_1000Combo , 0 );
else if ( ( iOldCombo / 100 ) < ( iCurCombo / 100 ) && iCurCombo > 1000 )
SCREENMAN -> PostMessageToTopScreen ( SM_ComboContinuing , 0 );
#undef CROSSED
2003-09-06 02:20:09 +00:00
// new max combo
2003-12-23 00:26:00 +00:00
g_CurStageStats . iMaxCombo [ m_PlayerNumber ] = max ( g_CurStageStats . iMaxCombo [ m_PlayerNumber ], iCurCombo );
2003-09-06 02:20:09 +00:00
2003-10-24 05:19:08 +00:00
/* Use the real current beat, not the beat we've been passed. That's because we
* want to record the current life/combo to the current time; eg. if it's a MISS,
* the beat we're registering is in the past, but the life is changing now. */
2004-07-24 18:11:04 +00:00
g_CurStageStats . UpdateComboList ( m_PlayerNumber , g_CurStageStats . fAliveSeconds [ m_PlayerNumber ], false );
2003-10-24 05:19:08 +00:00
2003-10-26 08:37:25 +00:00
float life = - 1 ;
2003-10-24 05:19:08 +00:00
if ( m_pLifeMeter )
life = m_pLifeMeter -> GetLife ();
2003-12-17 03:59:08 +00:00
else if ( m_pCombinedLifeMeter )
{
life = GAMESTATE -> m_fTugLifePercentP1 ;
if ( m_PlayerNumber == PLAYER_2 )
life = 1.0f - life ;
}
2003-10-26 08:37:25 +00:00
if ( life != - 1 )
2004-07-24 18:11:04 +00:00
g_CurStageStats . SetLifeRecordAt ( m_PlayerNumber , life , g_CurStageStats . fAliveSeconds [ m_PlayerNumber ] );
2003-10-23 06:17:40 +00:00
2003-11-26 06:40:03 +00:00
if ( m_pScoreDisplay )
2003-12-23 00:26:00 +00:00
m_pScoreDisplay -> SetScore ( g_CurStageStats . iScore [ m_PlayerNumber ]);
2003-11-26 06:40:03 +00:00
if ( m_pSecondaryScoreDisplay )
2003-12-23 00:26:00 +00:00
m_pSecondaryScoreDisplay -> SetScore ( g_CurStageStats . iScore [ m_PlayerNumber ]);
2002-11-03 20:10:17 +00:00
if ( m_pLifeMeter ) {
2003-03-16 17:45:32 +00:00
m_pLifeMeter -> ChangeLife ( scoreOfLastTap );
2002-11-03 20:10:17 +00:00
m_pLifeMeter -> OnDancePointsChange (); // update oni life meter
}
2003-06-30 18:08:27 +00:00
if ( m_pCombinedLifeMeter ) {
m_pCombinedLifeMeter -> ChangeLife ( m_PlayerNumber , scoreOfLastTap );
m_pCombinedLifeMeter -> OnDancePointsChange ( m_PlayerNumber ); // update oni life meter
}
2002-06-24 22:04:31 +00:00
}
2002-07-28 20:28:37 +00:00
2002-10-24 19:21:05 +00:00
2003-08-24 17:04:21 +00:00
void PlayerMinus :: HandleHoldScore ( HoldNoteScore holdScore , TapNoteScore tapScore )
2002-07-28 20:28:37 +00:00
{
2003-04-21 04:11:01 +00:00
bool NoCheating = true ;
2003-06-18 04:25:49 +00:00
#ifdef DEBUG
NoCheating = false ;
#endif
2003-04-21 04:11:01 +00:00
if ( GAMESTATE -> m_bDemonstrationOrJukebox )
NoCheating = false ;
// don't accumulate points if AutoPlay is on.
2003-05-17 21:19:00 +00:00
if ( NoCheating && GAMESTATE -> m_PlayerController [ m_PlayerNumber ] == PC_AUTOPLAY )
2003-04-21 04:11:01 +00:00
return ;
2003-06-30 18:08:27 +00:00
if ( m_pPrimaryScoreKeeper )
2003-07-08 20:56:25 +00:00
m_pPrimaryScoreKeeper -> HandleHoldScore ( holdScore , tapScore );
2003-06-30 18:08:27 +00:00
if ( m_pSecondaryScoreKeeper )
2003-07-08 20:56:25 +00:00
m_pSecondaryScoreKeeper -> HandleHoldScore ( holdScore , tapScore );
2002-08-23 20:18:29 +00:00
2003-11-26 06:40:03 +00:00
if ( m_pScoreDisplay )
2003-12-23 00:26:00 +00:00
m_pScoreDisplay -> SetScore ( g_CurStageStats . iScore [ m_PlayerNumber ]);
2003-11-26 06:40:03 +00:00
if ( m_pSecondaryScoreDisplay )
2003-12-23 00:26:00 +00:00
m_pSecondaryScoreDisplay -> SetScore ( g_CurStageStats . iScore [ m_PlayerNumber ]);
2002-11-03 20:52:13 +00:00
2002-10-12 22:04:44 +00:00
if ( m_pLifeMeter ) {
2003-03-16 17:45:32 +00:00
m_pLifeMeter -> ChangeLife ( holdScore , tapScore );
2003-02-17 03:45:52 +00:00
m_pLifeMeter -> OnDancePointsChange ();
2002-10-12 22:04:44 +00:00
}
2003-06-30 18:08:27 +00:00
if ( m_pCombinedLifeMeter ) {
m_pCombinedLifeMeter -> ChangeLife ( m_PlayerNumber , holdScore , tapScore );
m_pCombinedLifeMeter -> OnDancePointsChange ( m_PlayerNumber );
}
2002-07-28 20:28:37 +00:00
}
2002-09-05 03:45:07 +00:00
2003-08-24 17:04:21 +00:00
float PlayerMinus :: GetMaxStepDistanceSeconds ()
2002-07-28 20:28:37 +00:00
{
2004-06-27 00:03:06 +00:00
return GAMESTATE -> m_SongOptions . m_fMusicRate * ADJUSTED_WINDOW ( Boo );
2002-09-05 03:45:07 +00:00
}
2003-08-24 17:04:21 +00:00
void PlayerMinus :: FadeToFail ()
2002-09-05 03:45:07 +00:00
{
2003-08-24 17:04:21 +00:00
m_pNoteField -> FadeToFail ();
}
2004-02-11 05:57:51 +00:00
/* XXX: Why's m_NoteField in a separate class, again? Is that still needed? */
2003-11-26 06:40:03 +00:00
void Player :: Load ( PlayerNumber player_no , const NoteData * pNoteData , LifeMeter * pLM , CombinedLifeMeter * pCombinedLM , ScoreDisplay * pScoreDisplay , ScoreDisplay * pSecondaryScoreDisplay , Inventory * pInventory , ScoreKeeper * pPrimaryScoreKeeper , ScoreKeeper * pSecondaryScoreKeeper )
2003-08-24 17:04:21 +00:00
{
2003-11-26 06:40:03 +00:00
PlayerMinus :: Load ( player_no , pNoteData , pLM , pCombinedLM , pScoreDisplay , pSecondaryScoreDisplay , pInventory , pPrimaryScoreKeeper , pSecondaryScoreKeeper , & m_NoteField );
2002-09-05 03:45:07 +00:00
}
2004-06-08 00:08:04 +00:00
/*
* (c) 2001-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.
*/