2001-11-03 10:52:42 +00:00
#include "stdafx.h"
2001-11-04 19:34:28 +00:00
/*
-----------------------------------------------------------------------------
2002-06-24 22:04:31 +00:00
Class: Player
2001-11-03 10:52:42 +00:00
2002-03-19 07:09:49 +00:00
Desc: See header.
2001-11-03 10:52:42 +00:00
2002-05-19 01:59:48 +00:00
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
2002-06-24 22:04:31 +00:00
Chris Danford
2001-11-04 19:34:28 +00:00
-----------------------------------------------------------------------------
*/
2002-05-01 19:14:55 +00:00
#include "GameConstantsAndTypes.h"
2001-11-03 10:52:42 +00:00
#include "Math.h" // for fabs()
#include "Player.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-02-24 10:31:20 +00:00
#include "ArrowEffects.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-07-28 20:28:37 +00:00
#include "RageLog.h"
2001-11-03 10:52:42 +00:00
2002-08-28 22:42:40 +00:00
#define JUDGE_PERFECT_ZOOM_X THEME->GetMetricF("Player","JudgePerfectZoomX")
#define JUDGE_PERFECT_ZOOM_Y THEME->GetMetricF("Player","JudgePerfectZoomY")
#define JUDGE_GREAT_ZOOM_X THEME->GetMetricF("Player","JudgeGreatZoomX")
#define JUDGE_GREAT_ZOOM_Y THEME->GetMetricF("Player","JudgeGreatZoomY")
#define JUDGE_GOOD_ZOOM_X THEME->GetMetricF("Player","JudgeGoodZoomX")
#define JUDGE_GOOD_ZOOM_Y THEME->GetMetricF("Player","JudgeGoodZoomY")
#define JUDGE_BOO_ZOOM_X THEME->GetMetricF("Player","JudgeBooZoomX")
#define JUDGE_BOO_ZOOM_Y THEME->GetMetricF("Player","JudgeBooZoomY")
#define COMBO_JUDGE_TWEEN_SECONDS THEME->GetMetricF("Player","ComboJudgeTweenSeconds")
2002-08-29 01:38:21 +00:00
#define BRIGHT_GHOST_THRESHOLD THEME->GetMetricI("Player","BrightGhostThreshold")
2002-08-28 22:42:40 +00:00
// cache because reading from theme metrics is slow
float g_fJudgePerfectZoomX ,
g_fJudgePerfectZoomY ,
g_fJudgeGreatZoomX ,
g_fJudgeGreatZoomY ,
g_fJudgeGoodZoomX ,
g_fJudgeGoodZoomY ,
g_fJudgeBooZoomX ,
g_fJudgeBooZoomY ,
g_fComboJudgeTweenSeconds ;
2002-08-29 01:38:21 +00:00
int g_iBrightGhostThreshold ;
2002-08-28 22:42:40 +00:00
2002-05-19 01:59:48 +00:00
// these two items are in the
const float FRAME_JUDGE_AND_COMBO_Y = CENTER_Y ;
const float JUDGEMENT_Y_OFFSET = - 26 ;
const float COMBO_Y_OFFSET = + 26 ;
2002-07-28 20:28:37 +00:00
const float FRAME_JUDGE_AND_COMBO_BEAT_TIME = 0.2f ;
2002-02-24 01:43:11 +00:00
const float ARROWS_Y = SCREEN_TOP + ARROW_SIZE * 1.5f ;
const float HOLD_JUDGEMENT_Y = ARROWS_Y + 80 ;
2001-11-04 19:34:28 +00:00
2002-06-24 22:04:31 +00:00
const float HOLD_ARROW_NG_TIME = 0.18f ;
2001-11-04 19:34:28 +00:00
2002-02-24 01:43:11 +00:00
2002-04-03 21:33:51 +00:00
Player :: Player ()
2001-11-03 10:52:42 +00:00
{
2002-08-28 22:42:40 +00:00
// Update theme metrics cache
g_fJudgePerfectZoomX = JUDGE_PERFECT_ZOOM_X ;
g_fJudgePerfectZoomY = JUDGE_PERFECT_ZOOM_Y ;
g_fJudgeGreatZoomX = JUDGE_GREAT_ZOOM_X ;
g_fJudgeGreatZoomY = JUDGE_GREAT_ZOOM_Y ;
g_fJudgeGoodZoomX = JUDGE_GOOD_ZOOM_X ;
g_fJudgeGoodZoomY = JUDGE_GOOD_ZOOM_Y ;
g_fJudgeBooZoomX = JUDGE_BOO_ZOOM_X ;
g_fJudgeBooZoomY = JUDGE_BOO_ZOOM_Y ;
g_fComboJudgeTweenSeconds = COMBO_JUDGE_TWEEN_SECONDS ;
2002-08-29 01:38:21 +00:00
g_iBrightGhostThreshold = BRIGHT_GHOST_THRESHOLD ;
2002-08-28 22:42:40 +00:00
2002-06-24 22:04:31 +00:00
m_PlayerNumber = PLAYER_INVALID ;
2001-11-03 10:52:42 +00:00
2002-04-28 20:42:32 +00:00
m_pLifeMeter = NULL ;
m_pScore = NULL ;
2001-11-06 02:31:28 +00:00
2002-10-01 01:53:56 +00:00
m_iOffsetSample = 0 ;
2002-09-02 21:59:58 +00:00
this -> AddChild ( & m_GrayArrowRow );
this -> AddChild ( & m_NoteField );
this -> AddChild ( & m_GhostArrowRow );
2002-05-19 01:59:48 +00:00
2002-09-02 21:59:58 +00:00
m_frameJudgement . AddChild ( & m_Judgement );
this -> AddChild ( & m_frameJudgement );
2002-07-23 01:41:40 +00:00
2002-09-02 21:59:58 +00:00
m_frameCombo . AddChild ( & m_Combo );
this -> AddChild ( & m_frameCombo );
2002-05-19 01:59:48 +00:00
2002-04-16 17:31:00 +00:00
for ( int c = 0 ; c < MAX_NOTE_TRACKS ; c ++ )
2002-09-02 21:59:58 +00:00
this -> AddChild ( & m_HoldJudgement [ c ] );
2001-11-03 10:52:42 +00:00
}
2002-07-28 20:28:37 +00:00
void Player :: Load ( PlayerNumber pn , NoteData * pNoteData , LifeMeter * pLM , ScoreDisplay * pScore )
2002-02-02 05:11:12 +00:00
{
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Player::Load()", );
2002-07-28 20:28:37 +00:00
m_PlayerNumber = pn ;
m_pLifeMeter = pLM ;
m_pScore = pScore ;
2002-08-22 23:03:39 +00:00
const StyleDef * pStyleDef = GAMESTATE -> GetCurrentStyleDef ();
2002-07-28 20:28:37 +00:00
2002-08-01 21:55:40 +00:00
// init scoring
NoteDataWithScoring :: Init ();
2002-07-28 20:28:37 +00:00
// copy note data
2002-04-16 17:31:00 +00:00
this -> CopyAll ( pNoteData );
2002-08-01 21:55:40 +00:00
if ( GAMESTATE -> m_SongOptions . m_LifeType == SongOptions :: LIFE_BATTERY && GAMESTATE -> m_fSecondsBeforeFail [ m_PlayerNumber ] != - 1 ) // Oni dead
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 */
m_frameJudgement . StopTweening ();
2002-09-30 02:25:07 +00:00
// m_Combo.Reset(); // don't reset combos between songs in a course!
2002-08-26 08:11:52 +00:00
m_Judgement . Reset ();
2002-07-28 20:28:37 +00:00
m_iNumTapNotes = pNoteData -> GetNumTapNotes ();
m_iTapNotesHit = 0 ;
2002-10-24 23:22:43 +00:00
m_lScore = 0 ;
2002-08-13 23:26:46 +00:00
m_iMeter = GAMESTATE -> m_pCurNotes [ m_PlayerNumber ] ? GAMESTATE -> m_pCurNotes [ m_PlayerNumber ] -> m_iMeter : 5 ;
2002-10-25 04:45:16 +00:00
m_fScoreMultiplier = float ( m_iMeter * 1000000 ) / float (( m_iNumTapNotes * ( m_iNumTapNotes + 1 )) / 2 );
2002-10-24 23:22:43 +00:00
ASSERT ( m_fScoreMultiplier >= 0.0 );
2002-02-02 05:11:12 +00:00
2002-06-14 22:25:22 +00:00
if ( m_pScore )
2002-07-28 20:28:37 +00:00
m_pScore -> Init ( pn );
2002-04-28 20:42:32 +00:00
2002-07-28 20:28:37 +00:00
if ( ! GAMESTATE -> m_PlayerOptions [ pn ]. m_bHoldNotes )
2002-04-16 17:31:00 +00:00
this -> RemoveHoldNotes ();
2001-12-20 12:37:38 +00:00
2002-07-28 20:28:37 +00:00
this -> Turn ( GAMESTATE -> m_PlayerOptions [ pn ]. m_TurnType );
2001-11-04 19:34:28 +00:00
2002-07-28 20:28:37 +00:00
if ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bLittle )
2002-04-16 17:31:00 +00:00
this -> MakeLittle ();
2001-11-04 19:34:28 +00:00
2002-08-13 23:26:46 +00:00
int iPixelsToDrawBefore = 96 ;
int iPixelsToDrawAfter = 384 ;
2002-08-31 03:04:38 +00:00
// If both options are on, we *do* need to multiply it twice.
2002-09-30 02:19:02 +00:00
if ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bEffects [ PlayerOptions :: EFFECT_MINI ] )
2002-07-27 19:29:51 +00:00
{
2002-08-31 03:04:38 +00:00
iPixelsToDrawBefore *= 2 ;
iPixelsToDrawAfter *= 2 ;
}
2002-09-30 02:19:02 +00:00
if ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bEffects [ PlayerOptions :: EFFECT_SPACE ] )
2002-08-31 03:04:38 +00:00
{
iPixelsToDrawBefore *= 2 ;
iPixelsToDrawAfter *= 2 ;
2002-07-27 19:29:51 +00:00
}
2002-07-28 20:28:37 +00:00
m_NoteField . Load ( ( NoteData * ) this , pn , iPixelsToDrawBefore , iPixelsToDrawAfter );
2002-05-19 01:59:48 +00:00
2002-07-28 20:28:37 +00:00
m_GrayArrowRow . Load ( pn );
m_GhostArrowRow . Load ( pn );
2002-02-24 01:43:11 +00:00
2002-07-23 01:41:40 +00:00
m_frameJudgement . SetY ( FRAME_JUDGE_AND_COMBO_Y );
m_frameCombo . SetY ( FRAME_JUDGE_AND_COMBO_Y );
2002-07-28 20:28:37 +00:00
m_Combo . SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? - COMBO_Y_OFFSET : COMBO_Y_OFFSET );
m_Judgement . SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? - JUDGEMENT_Y_OFFSET : JUDGEMENT_Y_OFFSET );
2002-02-24 01:43:11 +00:00
2002-06-24 22:04:31 +00:00
for ( int c = 0 ; c < pStyleDef -> m_iColsPerPlayer ; c ++ )
2002-07-28 20:28:37 +00:00
m_HoldJudgement [ c ]. SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? SCREEN_HEIGHT - HOLD_JUDGEMENT_Y : HOLD_JUDGEMENT_Y );
2002-06-24 22:04:31 +00:00
for ( c = 0 ; c < pStyleDef -> m_iColsPerPlayer ; c ++ )
2002-07-28 20:28:37 +00:00
m_HoldJudgement [ c ]. SetX ( ( float ) pStyleDef -> m_ColumnInfo [ pn ][ c ]. fXOffset );
2002-02-24 01:43:11 +00:00
2002-07-28 20:28:37 +00:00
m_NoteField . SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? SCREEN_HEIGHT - ARROWS_Y : ARROWS_Y );
m_GrayArrowRow . SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? SCREEN_HEIGHT - ARROWS_Y : ARROWS_Y );
m_GhostArrowRow . SetY ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bReverseScroll ? SCREEN_HEIGHT - ARROWS_Y : ARROWS_Y );
2002-07-27 19:29:51 +00:00
2002-09-30 02:19:02 +00:00
if ( GAMESTATE -> m_PlayerOptions [ pn ]. m_bEffects [ PlayerOptions :: EFFECT_MINI ] )
2002-07-27 19:29:51 +00:00
{
m_NoteField . SetZoom ( 0.5f );
m_GrayArrowRow . SetZoom ( 0.5f );
m_GhostArrowRow . SetZoom ( 0.5f );
}
2001-11-04 19:34:28 +00:00
}
2002-07-28 20:28:37 +00:00
void Player :: Update ( float fDeltaTime )
2001-11-03 10:52:42 +00:00
{
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Player::Update(%f)", fDeltaTime );
2001-11-03 10:52:42 +00:00
2002-08-01 21:55:40 +00:00
2002-07-28 20:28:37 +00:00
const float fSongBeat = GAMESTATE -> m_fSongBeat ;
2002-06-23 11:43:53 +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
//
2002-09-07 10:35:26 +00:00
UpdateTapNotesMissedOlderThan ( GAMESTATE -> m_fSongBeat - GetMaxBeatDifference () );
2001-11-04 19:34:28 +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-03-19 07:09:49 +00:00
for ( int i = 0 ; i < m_iNumHoldNotes ; i ++ ) // for each HoldNote
2001-12-19 01:50:57 +00:00
{
2002-03-19 07:09:49 +00:00
HoldNote & hn = m_HoldNotes [ i ];
HoldNoteScore & hns = m_HoldNoteScores [ i ];
2002-06-14 22:25:22 +00:00
float & fLife = m_fHoldNoteLife [ i ];
2002-08-13 23:26:46 +00:00
int iHoldStartIndex = BeatToNoteRow ( hn . m_fStartBeat );
m_NoteField . m_bIsHoldingHoldNote [ i ] = false ; // set host flag so NoteField can do intelligent drawing
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
2001-12-19 01:50:57 +00:00
2002-04-16 17:31:00 +00:00
const StyleInput StyleI ( m_PlayerNumber , hn . m_iTrack );
2002-07-23 01:41:40 +00:00
const GameInput GameI = GAMESTATE -> GetCurrentStyleDef () -> 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
const TapNoteScore tns = m_TapNoteScores [ hn . m_iTrack ][ iHoldStartIndex ];
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
2002-08-13 23:26:46 +00:00
if ( hn . m_fStartBeat < fSongBeat && fSongBeat < hn . m_fEndBeat ) // if the song beat is in the range of this hold
2001-12-19 01:50:57 +00:00
{
2002-10-20 17:18:05 +00:00
bool bIsHoldingButton = INPUTMAPPER -> IsButtonDown ( GameI );
if ( ! GAMESTATE -> m_bEditing && ( PREFSMAN -> m_bAutoPlay || GAMESTATE -> m_bDemonstration ) )
bIsHoldingButton = true ;
2002-04-16 17:31:00 +00:00
2002-08-13 23:26:46 +00:00
m_NoteField . m_bIsHoldingHoldNote [ i ] = bIsHoldingButton && bSteppedOnTapNote ; // set host flag so NoteField can do intelligent drawing
2002-06-14 22:25:22 +00:00
2002-09-15 19:38:44 +00:00
if ( bSteppedOnTapNote ) // this note is not judged and we stepped on its head
{
m_NoteField . m_HoldNotes [ i ]. m_fStartBeat = fSongBeat ; // move the start of this Hold
}
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
fLife += fDeltaTime / HOLD_ARROW_NG_TIME ;
fLife = min ( fLife , 1 ); // clamp
2002-06-24 22:04:31 +00:00
m_GhostArrowRow . HoldNote ( hn . m_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
{
2002-08-13 23:26:46 +00:00
if ( fSongBeat - hn . m_fStartBeat > GetMaxBeatDifference () )
2002-06-14 22:25:22 +00:00
{
// Decrease life
fLife -= fDeltaTime / HOLD_ARROW_NG_TIME ;
fLife = max ( fLife , 0 ); // clamp
}
2002-02-24 01:43:11 +00:00
}
2002-08-01 03:15:27 +00:00
m_NoteField . m_fHoldNoteLife [ i ] = fLife ; // update the NoteField display
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!
2001-12-20 21:08:45 +00:00
{
2002-06-14 22:25:22 +00:00
hns = HNS_NG ;
2002-10-12 22:04:44 +00:00
HandleNoteScore ( hns , tns );
2002-06-24 22:04:31 +00:00
m_HoldJudgement [ hn . m_iTrack ]. SetHoldJudgement ( HNS_NG );
2002-08-01 03:15:27 +00:00
m_NoteField . m_HoldNoteScores [ i ] = HNS_NG ; // update the NoteField display
2001-12-19 01:50:57 +00:00
}
2002-02-24 01:43:11 +00:00
// check for OK
2002-10-12 22:17:16 +00:00
if ( fSongBeat >= hn . m_fEndBeat && 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 ;
2002-10-12 22:04:44 +00:00
HandleNoteScore ( hns , tns );
2002-09-05 03:45:07 +00:00
m_GhostArrowRow . TapNote ( StyleI . col , TNS_PERFECT , true ); // bright ghost flash
2002-06-24 22:04:31 +00:00
m_HoldJudgement [ hn . m_iTrack ]. SetHoldJudgement ( HNS_OK );
2002-08-01 03:15:27 +00:00
m_NoteField . m_fHoldNoteLife [ i ] = fLife ; // update the NoteField display
m_NoteField . m_HoldNoteScores [ i ] = HNS_OK ; // update the NoteField display
2001-12-19 01:50:57 +00:00
}
}
2002-02-24 01:43:11 +00:00
ActorFrame :: Update ( fDeltaTime );
2001-11-03 10:52:42 +00:00
}
2002-05-19 01:59:48 +00:00
void Player :: DrawPrimitives ()
2002-02-24 10:31:20 +00:00
{
2002-07-31 19:40:40 +00:00
m_frameCombo . Draw (); // draw this below everything else
2002-02-24 10:31:20 +00:00
D3DXMATRIX matOldView , matOldProj ;
2002-09-30 02:19:02 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_bEffects [ PlayerOptions :: EFFECT_SPACE ] )
2002-02-24 10:31:20 +00:00
{
// save old view and projection
2002-08-18 16:19:26 +00:00
DISPLAY -> GetViewTransform ( & matOldView );
DISPLAY -> GetProjectionTransform ( & matOldProj );
2002-02-24 10:31:20 +00:00
// construct view and project matrix
D3DXMATRIX matNewView ;
2002-07-28 20:28:37 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_bReverseScroll )
2002-07-27 19:29:51 +00:00
D3DXMatrixLookAtLH (
& matNewView ,
& D3DXVECTOR3 ( CENTER_X , GetY () - 300.0f , 400.0f ),
& D3DXVECTOR3 ( CENTER_X , GetY () + 100.0f , 0.0f ),
& D3DXVECTOR3 ( 0.0f , - 1.0f , 0.0f )
);
else
D3DXMatrixLookAtLH (
& matNewView ,
& D3DXVECTOR3 ( CENTER_X , GetY () + 800.0f , 400.0f ),
& D3DXVECTOR3 ( CENTER_X , GetY () + 400.0f , 0.0f ),
& D3DXVECTOR3 ( 0.0f , - 1.0f , 0.0f )
);
2002-08-18 16:19:26 +00:00
DISPLAY -> SetViewTransform ( & matNewView );
2002-02-24 10:31:20 +00:00
D3DXMATRIX matNewProj ;
D3DXMatrixPerspectiveFovLH ( & matNewProj , D3DX_PI / 4.0f , SCREEN_WIDTH / ( float ) SCREEN_HEIGHT , 0.0f , 1000.0f );
2002-08-18 16:19:26 +00:00
DISPLAY -> SetProjectionTransform ( & matNewProj );
2002-02-24 10:31:20 +00:00
}
2002-04-16 17:31:00 +00:00
m_GrayArrowRow . Draw ();
m_NoteField . Draw ();
m_GhostArrowRow . Draw ();
2002-02-24 10:31:20 +00:00
2002-09-30 02:19:02 +00:00
if ( GAMESTATE -> m_PlayerOptions [ m_PlayerNumber ]. m_bEffects [ PlayerOptions :: EFFECT_SPACE ] )
2002-02-24 10:31:20 +00:00
{
// restire old view and projection
2002-08-18 16:19:26 +00:00
DISPLAY -> SetViewTransform ( & matOldView );
DISPLAY -> SetProjectionTransform ( & matOldProj );
2002-02-24 10:31:20 +00:00
}
2002-07-23 01:41:40 +00:00
m_frameJudgement . Draw ();
2002-02-24 10:31:20 +00:00
2002-04-16 17:31:00 +00:00
for ( int c = 0 ; c < m_iNumTracks ; c ++ )
2002-02-24 10:31:20 +00:00
m_HoldJudgement [ c ]. Draw ();
2001-12-28 10:15:59 +00:00
}
2002-07-28 20:28:37 +00:00
void Player :: Step ( int col )
2001-11-03 10:52:42 +00:00
{
2002-08-01 21:55:40 +00:00
if ( GAMESTATE -> m_SongOptions . m_LifeType == SongOptions :: LIFE_BATTERY && GAMESTATE -> m_fSecondsBeforeFail [ m_PlayerNumber ] != - 1 ) // Oni dead
return ; // do nothing
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Player::HandlePlayerStep()" );
2001-11-03 10:52:42 +00:00
2002-04-16 17:31:00 +00:00
ASSERT ( col >= 0 && col <= m_iNumTracks );
2002-03-09 06:19:40 +00:00
2002-07-28 20:28:37 +00:00
const float fSongBeat = GAMESTATE -> m_fSongBeat ;
2001-11-04 19:34:28 +00:00
// look for the closest matching step
2002-07-28 20:28:37 +00:00
int iIndexStartLookingAt = BeatToNoteRow ( GAMESTATE -> m_fSongBeat );
2002-10-01 01:53:56 +00:00
// number of elements to examine on either end of iIndexStartLookingAt
// 3dfsux: I expanded this window so that beat correction will look
int iNumElementsToExamine = BeatToNoteRow ( GAMESTATE -> m_fCurBPS * GAMESTATE -> m_SongOptions . m_fMusicRate );
2001-11-03 10:52:42 +00:00
2002-07-31 19:40:40 +00:00
//LOG->Trace( "iIndexStartLookingAt = %d, iNumElementsToExamine = %d", iIndexStartLookingAt, iNumElementsToExamine );
2001-11-03 10:52:42 +00:00
2002-05-29 09:47:24 +00:00
int iIndexOverlappingNote = - 1 ; // leave as -1 if we don't find any
2002-08-24 22:49:10 +00:00
// Start at iIndexStartLookingAt and search outward. The first one note
// overlaps the player's hit (this is the closest match).
2001-11-04 19:34:28 +00:00
for ( int delta = 0 ; delta <= iNumElementsToExamine ; delta ++ )
{
int iCurrentIndexEarlier = iIndexStartLookingAt - delta ;
int iCurrentIndexLater = iIndexStartLookingAt + delta ;
2001-11-03 10:52:42 +00:00
2001-11-04 19:34:28 +00:00
////////////////////////////
// check the step to the left of iIndexStartLookingAt
////////////////////////////
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Checking Notes[%d]", iCurrentIndexEarlier );
2002-08-17 06:44:04 +00:00
if ( iCurrentIndexEarlier >= 0 &&
2002-10-25 04:45:16 +00:00
GetTapNote ( col , iCurrentIndexEarlier ) != TAP_EMPTY && // there is a note here
2002-06-24 22:04:31 +00:00
m_TapNoteScores [ col ][ iCurrentIndexEarlier ] == TNS_NONE ) // this note doesn't have a score
2001-11-04 19:34:28 +00:00
{
2002-05-29 09:47:24 +00:00
iIndexOverlappingNote = iCurrentIndexEarlier ;
break ;
2001-11-04 19:34:28 +00:00
}
2001-11-03 10:52:42 +00:00
2002-05-29 09:47:24 +00:00
2001-11-04 19:34:28 +00:00
////////////////////////////
// check the step to the right of iIndexStartLookingAt
////////////////////////////
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Checking Notes[%d]", iCurrentIndexLater );
2002-08-17 06:44:04 +00:00
if ( iCurrentIndexLater >= 0 &&
2002-10-25 04:45:16 +00:00
GetTapNote ( col , iCurrentIndexLater ) != TAP_EMPTY && // there is a note here
2002-06-24 22:04:31 +00:00
m_TapNoteScores [ col ][ iCurrentIndexLater ] == TNS_NONE ) // this note doesn't have a score
2001-11-04 19:34:28 +00:00
{
2002-05-29 09:47:24 +00:00
iIndexOverlappingNote = iCurrentIndexLater ;
break ;
2001-11-04 19:34:28 +00:00
}
}
2002-05-29 09:47:24 +00:00
2002-07-11 19:02:26 +00:00
bool bDestroyedNote = false ;
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
const float fStepBeat = NoteRowToBeat ( ( float ) iIndexOverlappingNote );
const float fBeatsUntilStep = fStepBeat - fSongBeat ;
2002-07-28 20:28:37 +00:00
const float fPercentFromPerfect = fabsf ( fBeatsUntilStep / GetMaxBeatDifference () );
2002-10-01 01:53:56 +00:00
const float fNoteOffset = fBeatsUntilStep / GAMESTATE -> m_fCurBPS ; //the offset from the actual step in seconds
2002-06-24 22:04:31 +00:00
TapNoteScore & score = m_TapNoteScores [ col ][ iIndexOverlappingNote ];
2002-09-22 18:18:50 +00:00
if ( fPercentFromPerfect < PREFSMAN -> m_fJudgeWindowPerfectPercent ) score = TNS_PERFECT ;
else if ( fPercentFromPerfect < PREFSMAN -> m_fJudgeWindowGreatPercent ) score = TNS_GREAT ;
else if ( fPercentFromPerfect < PREFSMAN -> m_fJudgeWindowGoodPercent ) score = TNS_GOOD ;
2002-10-01 01:53:56 +00:00
//we have to mark boo's as better than MISS's now that the window is expanded
else if ( fPercentFromPerfect < 1.0f ) score = TNS_BOO ;
else score = TNS_NONE ;
2002-06-24 22:04:31 +00:00
2002-08-27 16:53:25 +00:00
if ( GAMESTATE -> m_bDemonstration || PREFSMAN -> m_bAutoPlay )
score = TNS_PERFECT ;
2002-08-28 22:42:40 +00:00
bDestroyedNote = ( score >= TNS_GOOD );
2002-07-11 19:02:26 +00:00
2002-10-01 01:53:56 +00:00
LOG -> Trace ( "(%2d/%2d)Note offset: %f, Score: %i" , m_iOffsetSample , SAMPLE_COUNT , fNoteOffset , score );
2002-10-20 21:39:13 +00:00
if ( GAMESTATE -> m_SongOptions . m_AutoAdjust == SongOptions :: ADJUST_ON )
{
2002-10-01 01:53:56 +00:00
m_fOffset [ m_iOffsetSample ++ ] = fNoteOffset ;
2002-10-20 21:39:13 +00:00
if ( m_iOffsetSample >= SAMPLE_COUNT )
{
2002-10-24 07:09:54 +00:00
float mean = calc_mean ( m_fOffset , m_fOffset + SAMPLE_COUNT );
float stddev = calc_stddev ( m_fOffset , m_fOffset + SAMPLE_COUNT );
2002-06-24 22:04:31 +00:00
2002-10-24 07:09:54 +00:00
if ( stddev < .03 && stddev < fabsf ( mean )) { //If they stepped with less than .03 error
2002-10-01 01:53:56 +00:00
GAMESTATE -> m_pCurSong -> 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 ;
2002-06-24 22:04:31 +00:00
}
2002-05-29 09:47:24 +00:00
}
2002-10-01 01:53:56 +00:00
if ( score > TNS_NONE ) {
bool bRowDestroyed = true ;
for ( int t = 0 ; t < m_iNumTracks ; t ++ ) // did this complete the elminiation of the row?
{
2002-10-25 04:45:16 +00:00
if ( GetTapNote ( t , iIndexOverlappingNote ) != TAP_EMPTY && // there is a note here
2002-10-01 01:53:56 +00:00
m_TapNoteScores [ t ][ iIndexOverlappingNote ] == TNS_NONE ) // and it doesn't have a score
{
bRowDestroyed = false ;
break ; // stop searching
}
}
if ( bRowDestroyed )
2002-10-24 05:15:47 +00:00
OnRowDestroyed ( iIndexOverlappingNote );
2002-10-01 01:53:56 +00:00
}
2002-05-29 09:47:24 +00:00
}
2002-07-11 19:02:26 +00:00
if ( ! bDestroyedNote )
m_GrayArrowRow . Step ( col );
2001-11-04 19:34:28 +00:00
}
2001-11-03 10:52:42 +00:00
2002-10-24 05:15:47 +00:00
void Player :: OnRowDestroyed ( int iIndexThatWasSteppedOn )
2001-11-04 19:34:28 +00:00
{
2002-10-02 05:42:59 +00:00
LOG -> Trace ( "Player::OnRowDestroyed" );
2002-06-24 22:04:31 +00:00
// find the minimum score of the row
TapNoteScore score = TNS_PERFECT ;
for ( int t = 0 ; t < m_iNumTracks ; t ++ )
if ( m_TapNoteScores [ t ][ iIndexThatWasSteppedOn ] >= TNS_BOO )
score = min ( score , m_TapNoteScores [ t ][ iIndexThatWasSteppedOn ] );
2001-11-03 10:52:42 +00:00
2002-04-16 17:31:00 +00:00
// remove this row from the NoteField
2002-08-13 23:26:46 +00:00
// bool bHoldNoteOnThisBeat = false;
// for( int j=0; j<m_iNumHoldNotes; j++ )
// {
// if( m_HoldNotes[j].m_iStartIndex == iIndexThatWasSteppedOn )
// {
// bHoldNoteOnThisBeat = true;
// break;
// }
// }
2002-08-01 03:15:27 +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-08-13 23:26:46 +00:00
if ( score == TNS_PERFECT || score == TNS_GREAT )
2002-04-28 20:42:32 +00:00
m_NoteField . RemoveTapNoteRow ( iIndexThatWasSteppedOn );
2002-02-24 01:43:11 +00:00
2002-10-24 05:24:56 +00:00
int iNumNotesInThisRow = 0 ;
2002-04-16 17:31:00 +00:00
for ( int c = 0 ; c < m_iNumTracks ; c ++ ) // for each column
2002-02-24 01:43:11 +00:00
{
2002-10-25 04:45:16 +00:00
if ( GetTapNote ( c , iIndexThatWasSteppedOn ) != TAP_EMPTY ) // if there is a note in this col
2002-06-24 22:04:31 +00:00
{
2002-10-02 05:42:59 +00:00
iNumNotesInThisRow ++ ;
2002-10-24 05:24:56 +00:00
// show the ghost arrow for this column
m_GhostArrowRow . TapNote ( c , score , m_Combo . GetCurrentCombo () > g_iBrightGhostThreshold );
2002-10-02 05:42:59 +00:00
}
2002-10-24 05:24:56 +00:00
}
2002-10-02 05:42:59 +00:00
2002-10-24 05:24:56 +00:00
if ( iNumNotesInThisRow > 0 )
{
HandleNoteScore ( score , iNumNotesInThisRow ); // update score
m_Combo . UpdateScore ( score , iNumNotesInThisRow );
GAMESTATE -> m_iMaxCombo [ m_PlayerNumber ] = max ( GAMESTATE -> m_iMaxCombo [ m_PlayerNumber ], m_Combo . GetCurrentCombo () );
2001-11-03 10:52:42 +00:00
}
2002-05-19 01:59:48 +00:00
2002-07-28 20:28:37 +00:00
// update the judgement, score, and life
m_Judgement . SetJudgement ( score );
2002-05-19 01:59:48 +00:00
// zoom the judgement and combo like a heart beat
2002-09-07 10:31:07 +00:00
float fStartZoomX = 0.f , fStartZoomY = 0.f ;
2002-05-19 01:59:48 +00:00
switch ( score )
{
2002-08-28 22:42:40 +00:00
case TNS_PERFECT : fStartZoomX = g_fJudgePerfectZoomX ; fStartZoomY = g_fJudgePerfectZoomY ; break ;
case TNS_GREAT : fStartZoomX = g_fJudgeGreatZoomX ; fStartZoomY = g_fJudgeGreatZoomY ; break ;
case TNS_GOOD : fStartZoomX = g_fJudgeGoodZoomX ; fStartZoomY = g_fJudgeGoodZoomY ; break ;
case TNS_BOO : fStartZoomX = g_fJudgeBooZoomX ; fStartZoomY = g_fJudgeBooZoomY ; break ;
2002-05-19 01:59:48 +00:00
}
2002-09-15 19:31:33 +00:00
m_frameJudgement . StopTweening ();
2002-08-28 22:42:40 +00:00
m_frameJudgement . SetZoomX ( fStartZoomX );
m_frameJudgement . SetZoomY ( fStartZoomY );
m_frameJudgement . BeginTweening ( g_fComboJudgeTweenSeconds );
2002-07-23 01:41:40 +00:00
m_frameJudgement . SetTweenZoom ( 1 );
2002-09-15 19:31:33 +00:00
m_frameCombo . StopTweening ();
2002-08-28 22:42:40 +00:00
m_frameCombo . SetZoomX ( fStartZoomX );
m_frameCombo . SetZoomY ( fStartZoomY );
m_frameCombo . BeginTweening ( g_fComboJudgeTweenSeconds );
2002-07-23 01:41:40 +00:00
m_frameCombo . SetTweenZoom ( 1 );
2001-11-03 10:52:42 +00:00
}
2002-04-16 17:31:00 +00:00
int Player :: UpdateTapNotesMissedOlderThan ( float fMissIfOlderThanThisBeat )
2001-11-04 19:34:28 +00:00
{
2002-07-31 19:40:40 +00:00
//LOG->Trace( "Notes::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
2001-11-04 19:34:28 +00:00
2002-05-19 01:59:48 +00:00
int iMissIfOlderThanThisIndex = BeatToNoteRow ( fMissIfOlderThanThisBeat );
2001-11-03 10:52:42 +00:00
int iNumMissesFound = 0 ;
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.
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 );
2002-10-02 05:42:59 +00:00
for ( int r = iStartCheckingAt ; r < iMissIfOlderThanThisIndex ; r ++ )
2001-11-03 10:52:42 +00:00
{
2002-10-02 05:42:59 +00:00
int iNumMissesThisRow = 0 ;
for ( int t = 0 ; t < m_iNumTracks ; t ++ )
2001-11-03 10:52:42 +00:00
{
2002-10-25 04:45:16 +00:00
if ( GetTapNote ( t , r ) != TAP_EMPTY && m_TapNoteScores [ t ][ r ] == TNS_NONE )
2002-06-24 22:04:31 +00:00
{
m_TapNoteScores [ t ][ r ] = TNS_MISS ;
iNumMissesFound ++ ;
2002-10-02 05:42:59 +00:00
iNumMissesThisRow ++ ;
2002-06-24 22:04:31 +00:00
}
2001-11-03 10:52:42 +00:00
}
2002-10-02 05:42:59 +00:00
if ( iNumMissesThisRow > 0 )
{
HandleNoteScore ( TNS_MISS , iNumMissesThisRow );
m_Combo . UpdateScore ( TNS_MISS , iNumMissesThisRow );
}
2001-11-03 10:52:42 +00:00
}
2002-05-29 09:47:24 +00:00
if ( iNumMissesFound > 0 )
{
m_Judgement . SetJudgement ( TNS_MISS );
}
2001-11-03 10:52:42 +00:00
return iNumMissesFound ;
}
2002-07-28 20:28:37 +00:00
void Player :: CrossedRow ( int iNoteRow )
2002-06-24 22:04:31 +00:00
{
2002-08-02 09:31:06 +00:00
if ( PREFSMAN -> m_bAutoPlay || GAMESTATE -> m_bDemonstration )
2002-06-24 22:04:31 +00:00
{
// check to see if there's at the crossed row
for ( int t = 0 ; t < m_iNumTracks ; t ++ )
{
2002-10-25 04:45:16 +00:00
if ( GetTapNote ( t , iNoteRow ) != TAP_EMPTY )
2002-07-28 20:28:37 +00:00
this -> Step ( t );
2002-06-24 22:04:31 +00:00
}
}
}
2001-11-03 10:52:42 +00:00
2001-11-04 19:34:28 +00:00
2002-10-02 05:42:59 +00:00
void Player :: HandleNoteScore ( TapNoteScore score , int iNumTapsInRow )
2002-06-24 22:04:31 +00:00
{
2002-10-02 05:42:59 +00:00
ASSERT ( iNumTapsInRow >= 1 );
2002-08-23 20:18:29 +00:00
// don't accumulate points if AutoPlay is on.
if ( PREFSMAN -> m_bAutoPlay && ! GAMESTATE -> m_bDemonstration )
return ;
2002-07-28 20:28:37 +00:00
// update dance points for Oni lifemeter
2002-10-02 05:42:59 +00:00
GAMESTATE -> m_iActualDancePoints [ m_PlayerNumber ] += iNumTapsInRow * TapNoteScoreToDancePoints ( score );
GAMESTATE -> m_TapNoteScores [ m_PlayerNumber ][ score ] += iNumTapsInRow ;
2002-08-23 20:18:29 +00:00
if ( m_pLifeMeter )
m_pLifeMeter -> ChangeLife ( score );
2002-07-28 20:28:37 +00:00
if ( m_pLifeMeter )
2002-10-02 05:42:59 +00:00
m_pLifeMeter -> OnDancePointsChange (); // update oni life meter
2002-06-24 22:04:31 +00:00
2002-07-28 20:28:37 +00:00
//A single step's points are calculated as follows:
//
2002-10-24 23:06:37 +00:00
//p = score multiplier (Perfect = 10, Great = 5, other = 0)
2002-07-28 20:28:37 +00:00
//N = total number of steps and freeze steps
2002-10-24 23:06:37 +00:00
//S = The sum of all integers from 1 to N (the total number of steps/freeze steps)
2002-07-28 20:28:37 +00:00
//n = number of the current step or freeze step (varies from 1 to N)
//B = Base value of the song (1,000,000 X the number of feet difficulty) - All edit data is rated as 5 feet
//So, the score for one step is:
//one_step_score = p * (B/S) * n
//
//*IMPORTANT* : Double steps (U+L, D+R, etc.) count as two steps instead of one, so if you get a double L+R on the 112th step of a song, you score is calculated with a Perfect/Great/whatever for both the 112th and 113th steps. Got it? Now, through simple algebraic manipulation
//S = 1+...+N = (1+N)*N/2 (1 through N added together)
//Okay, time for an example:
//
//So, for example, suppose we wanted to calculate the step score of a "Great" on the 57th step of a 441 step, 8-foot difficulty song (I'm just making this one up):
//
//S = (1 + 441)*441 / 2
//= 194,222 / 2
//= 97,461
//StepScore = p * (B/S) * n
//= 5 * (8,000,000 / 97,461) * 57
//= 5 * (82) * 57 (The 82 is rounded down from 82.08411...)
//= 23,370
//Remember this is just the score for the step, not the cumulative score up to the 57th step. Also, please note that I am currently checking into rounding errors with the system and if there are any, how they are resolved in the system.
//
//Note: if you got all Perfect on this song, you would get (p=10)*B, which is 80,000,000. In fact, the maximum possible score for any song is the number of feet difficulty X 10,000,000.
2002-10-24 19:21:05 +00:00
//3dfsux:
//I redid this code so it will store the score as a long, then correct the score for each song based on that value.
// lScore == p * n
// m_fScoreMultiplier = (B/S)
// keeping these seperate for as long as possible improves accuracy.
2002-07-28 20:28:37 +00:00
int p ; // score multiplier
2002-10-24 19:21:05 +00:00
2002-07-28 20:28:37 +00:00
switch ( score )
2002-06-24 22:04:31 +00:00
{
2002-07-28 20:28:37 +00:00
case TNS_PERFECT : p = 10 ; break ;
case TNS_GREAT : p = 5 ; break ;
default : p = 0 ; break ;
2002-06-24 22:04:31 +00:00
}
2002-10-24 19:21:05 +00:00
2002-10-02 05:42:59 +00:00
for ( int i = 0 ; i < iNumTapsInRow ; i ++ )
2002-10-24 23:22:43 +00:00
m_lScore += p * ++ m_iTapNotesHit ;
ASSERT ( m_lScore >= 0 );
2002-10-24 19:21:05 +00:00
if ( m_pScore )
2002-10-24 23:22:43 +00:00
m_pScore -> SetScore ( GAMESTATE -> m_fScore [ m_PlayerNumber ] = m_lScore * m_fScoreMultiplier );
2002-06-24 22:04:31 +00:00
}
2002-07-28 20:28:37 +00:00
2002-10-24 19:21:05 +00:00
2002-10-12 22:04:44 +00:00
void Player :: HandleNoteScore ( HoldNoteScore score , TapNoteScore TapNoteScore )
2002-07-28 20:28:37 +00:00
{
2002-08-23 20:18:29 +00:00
// don't accumulate points if AutoPlay is on.
if ( PREFSMAN -> m_bAutoPlay && ! GAMESTATE -> m_bDemonstration )
return ;
2002-10-02 05:42:59 +00:00
// update dance points totals
2002-07-28 20:28:37 +00:00
GAMESTATE -> m_iActualDancePoints [ m_PlayerNumber ] += HoldNoteScoreToDancePoints ( score );
2002-10-02 05:42:59 +00:00
GAMESTATE -> m_HoldNoteScores [ m_PlayerNumber ][ score ] ++ ;
2002-08-23 20:18:29 +00:00
2002-10-12 22:04:44 +00:00
if ( m_pLifeMeter ) {
if ( score == HNS_NG ) {
m_pLifeMeter -> ChangeLife ( score , TapNoteScore );
// refresh Oni life meter
m_pLifeMeter -> OnDancePointsChange ();
}
}
2002-07-28 20:28:37 +00:00
}
2002-09-05 03:45:07 +00:00
2002-07-28 20:28:37 +00:00
float Player :: GetMaxBeatDifference ()
{
2002-09-22 18:18:50 +00:00
return GAMESTATE -> m_fCurBPS * PREFSMAN -> m_fJudgeWindowSeconds * GAMESTATE -> m_SongOptions . m_fMusicRate ;
2002-09-05 03:45:07 +00:00
}
void Player :: FadeToFail ()
{
m_NoteField . FadeToFail ();
}