Files
itgmania212121/stepmania/src/Player.cpp
T

984 lines
33 KiB
C++
Raw Normal View History

2003-02-16 04:01:45 +00:00
#include "global.h"
/*
-----------------------------------------------------------------------------
2002-06-24 22:04:31 +00:00
Class: Player
2001-11-03 10:52:42 +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-03 10:52:42 +00:00
#include "Player.h"
2002-05-01 19:14:55 +00:00
#include "GameConstantsAndTypes.h"
2003-02-14 06:42:08 +00:00
#include <math.h> // for fabs()
#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"
#include "RageMath.h"
#include "RageDisplay.h"
#include "ThemeManager.h"
#include "Combo.h"
2003-02-25 02:51:04 +00:00
#include "ScoreDisplay.h"
#include "LifeMeter.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"
#include "ScreenGameplay.h" /* for SM_ComboStopped */
#include "ScreenManager.h"
2001-11-03 10:52:42 +00:00
2003-08-14 08:13:08 +00:00
CachedThemeMetricF GRAY_ARROWS_Y_STANDARD ("Player","GrayArrowsYStandard");
CachedThemeMetricF GRAY_ARROWS_Y_REVERSE ("Player","GrayArrowsYReverse");
2003-04-14 22:12:54 +00:00
#define JUDGMENT_X( p, both_sides ) THEME->GetMetricF("Player",both_sides ? "JudgmentXOffsetBothSides" : ssprintf("JudgmentXOffsetOneSideP%d",p+1))
2003-02-19 06:42:29 +00:00
#define JUDGMENT_Y THEME->GetMetricF("Player","JudgmentY")
2003-04-14 22:12:54 +00:00
#define COMBO_X( p, both_sides ) THEME->GetMetricF("Player",both_sides ? "ComboXOffsetBothSides" : ssprintf("ComboXOffsetOneSideP%d",p+1))
2003-02-19 06:42:29 +00:00
#define COMBO_Y THEME->GetMetricF("Player","ComboY")
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-02-19 06:42:29 +00:00
#define START_DRAWING_AT_PIXELS THEME->GetMetricI("Player","StartDrawingAtPixels")
#define STOP_DRAWING_AT_PIXELS THEME->GetMetricI("Player","StopDrawingAtPixels")
2003-08-01 00:12:10 +00:00
#define MAX_PRO_TIMING_ERROR THEME->GetMetricI("Player","MaxProTimingError")
2001-11-03 10:52:42 +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
2003-04-07 21:24:14 +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();
2002-08-28 22:42:40 +00:00
2002-06-24 22:04:31 +00:00
m_PlayerNumber = PLAYER_INVALID;
2001-11-20 11:49:10 +00:00
2002-04-28 20:42:32 +00:00
m_pLifeMeter = NULL;
2003-06-30 18:08:27 +00:00
m_pCombinedLifeMeter = NULL;
2002-04-28 20:42:32 +00:00
m_pScore = 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
2002-10-01 01:53:56 +00:00
m_iOffsetSample = 0;
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 );
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-08-20 09:58:11 +00:00
PlayerAI::InitFromDisk();
2001-11-03 10:52:42 +00:00
}
PlayerMinus::~PlayerMinus()
2002-11-03 07:17:03 +00:00
{
}
void PlayerMinus::Load( PlayerNumber pn, NoteData* pNoteData, LifeMeter* pLM, CombinedLifeMeter* pCombinedLM, ScoreDisplay* pScore, Inventory* pInventory, ScoreKeeper* pPrimaryScoreKeeper, ScoreKeeper* pSecondaryScoreKeeper, NoteFieldPlus* pNoteField )
2002-02-02 05:11:12 +00:00
{
GAMESTATE->ResetNoteSkins();
//LOG->Trace( "PlayerMinus::Load()", );
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;
2002-07-28 20:28:37 +00:00
m_pScore = pScore;
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;
m_pNoteField = pNoteField;
2003-06-25 05:35:14 +00:00
m_iRowLastCrossed = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ) - 1;
// m_iRowLastCrossed = -1;
2002-07-28 20:28:37 +00:00
/* Ensure that this is up-to-date. */
2003-04-21 23:43:51 +00:00
GAMESTATE->m_pPosition->Load(pn);
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
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-01-27 06:18:13 +00:00
if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY && GAMESTATE->m_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 );
2003-02-17 12:19:42 +00:00
m_Judgment.Reset();
2002-07-28 20:28:37 +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
2003-08-13 19:17:28 +00:00
/* Apply transforms. */
NoteDataUtil::TransformNoteData( *this, GAMESTATE->m_PlayerOptions[pn], GAMESTATE->GetCurrentStyleDef()->m_StepsType );
2001-11-30 19:08:44 +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;
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 ) );
m_pNoteField->SetY( GRAY_ARROWS_Y_STANDARD );
m_pNoteField->Load( (NoteData*)this, pn, iStartDrawingAtPixels, iStopDrawingAtPixels, GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD );
2003-04-14 22:32:08 +00:00
m_ArrowBackdrop.SetPlayer( pn );
2001-12-20 12:37:38 +00:00
/* Cache note skins that are used as attacks. */
switch( GAMESTATE->m_PlayMode )
{
case PLAY_MODE_BATTLE:
case PLAY_MODE_RAVE:
for( int al=0; al<NUM_ATTACK_LEVELS; al++ )
{
Character *ch = GAMESTATE->m_pCurCharacters[m_PlayerNumber];
CString* asAttacks = ch->m_sAttacks[al];
for( int att = 0; att < NUM_ATTACKS_PER_LEVEL; ++att )
{
PlayerOptions po;
po.FromString( asAttacks[att] );
if( po.m_sNoteSkin != "" )
m_pNoteField->CacheNoteSkin( po.m_sNoteSkin );
}
}
}
2003-08-17 00:15:54 +00:00
const bool bReverse = GAMESTATE->m_PlayerOptions[pn].GetReversePercentForColumn(0) == 1;
2003-04-14 22:12:54 +00:00
bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyleDef()->m_StyleType==StyleDef::ONE_PLAYER_TWO_CREDITS;
m_Combo.SetX( COMBO_X(m_PlayerNumber,bPlayerUsingBothSides) );
2003-04-15 18:32:34 +00:00
m_Combo.SetY( bReverse ? SCREEN_BOTTOM-COMBO_Y : SCREEN_TOP+COMBO_Y );
2003-04-14 22:12:54 +00:00
m_Judgment.SetX( JUDGMENT_X(m_PlayerNumber,bPlayerUsingBothSides) );
2003-02-17 12:19:42 +00:00
m_Judgment.SetY( bReverse ? SCREEN_BOTTOM-JUDGMENT_Y : SCREEN_TOP+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 );
2001-12-20 12:37:38 +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 );
2002-11-16 09:12:55 +00:00
int c;
for( c=0; c<pStyleDef->m_iColsPerPlayer; c++ )
2003-02-17 12:19:42 +00:00
{
m_HoldJudgment[c].SetX( (float)pStyleDef->m_ColumnInfo[pn][c].fXOffset );
m_HoldJudgment[c].Command( g_NoteFieldMode[pn].m_HoldJudgmentCmd[c] );
2003-02-17 12:19:42 +00:00
}
2001-12-28 10:15:59 +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
m_soundMineExplosion.Load( THEME->GetPathToS("Player explosion") );
}
2001-11-03 10:52:42 +00:00
void PlayerMinus::Update( float fDeltaTime )
{
//LOG->Trace( "PlayerMinus::Update(%f)", fDeltaTime );
if( GAMESTATE->m_pCurSong==NULL )
return;
2002-07-28 20:28:37 +00:00
const float fSongBeat = GAMESTATE->m_fSongBeat;
2002-06-23 11:43:53 +00:00
m_pNoteField->Update( fDeltaTime );
2003-08-14 08:13:08 +00:00
//
// Update Y positions
//
2003-08-17 00:15:54 +00:00
float fPercentReverse = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].GetReversePercentForColumn(0);
float fHoldJudgeYPos = SCALE( fPercentReverse, 0.f, 1.f, HOLD_JUDGMENT_Y_STANDARD, HOLD_JUDGMENT_Y_REVERSE );
2003-08-14 08:13:08 +00:00
float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE );
int c;
for( c=0; c<GAMESTATE->GetCurrentStyleDef()->m_iColsPerPlayer; c++ )
m_HoldJudgment[c].SetY( fHoldJudgeYPos );
2003-08-14 08:13:08 +00:00
m_ArrowBackdrop.SetY( fGrayYPos );
// NoteField accounts for reverse on its own now.
// m_pNoteField->SetY( fGrayYPos );
2003-08-14 08:13:08 +00:00
float fMiniPercent = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_MINI];
2003-08-15 01:04:05 +00:00
float fZoom = 1 - fMiniPercent*0.5f;
m_pNoteField->SetZoom( fZoom );
2003-08-14 08:13:08 +00:00
2002-02-24 01:43:11 +00:00
//
// Check for TapNote misses
2002-02-24 01:43:11 +00:00
//
2003-02-09 04:05:31 +00:00
UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() );
2002-02-24 01:43:11 +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);
2002-12-18 22:25:24 +00:00
HoldNoteScore hns = GetHoldNoteScore(i);
float fLife = GetHoldNoteLife(i);
int iHoldStartIndex = BeatToNoteRow(hn.fStartBeat);
m_pNoteField->m_bIsHoldingHoldNote[i] = false; // set host flag so NoteField can do intelligent drawing
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
const StyleInput StyleI( m_PlayerNumber, hn.iTrack );
2002-07-23 01:41:40 +00:00
const GameInput GameI = GAMESTATE->GetCurrentStyleDef()->StyleInputToGameInput( StyleI );
2001-12-19 01:50:57 +00:00
// if they got a bad score or haven't stepped on the corresponding tap yet
const TapNoteScore tns = GetTapNoteScore(hn.iTrack, iHoldStartIndex);
const bool bSteppedOnTapNote = tns != TNS_NONE && tns != TNS_MISS; // did they step on the start of this hold?
// If the song beat is in the range of this hold:
if( hn.fStartBeat <= fSongBeat && fSongBeat <= hn.fEndBeat )
2001-12-19 01:50:57 +00:00
{
bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );
// TODO: Make the CPU miss sometimes.
if( GAMESTATE->m_PlayerController[m_PlayerNumber] != PC_HUMAN )
bIsHoldingButton = true;
2002-06-14 22:25:22 +00:00
2003-07-15 23:24:48 +00:00
// set hold flag so NoteField can do intelligent drawing
m_pNoteField->m_bIsHoldingHoldNote[i] = bIsHoldingButton && bSteppedOnTapNote;
2002-04-16 17:31:00 +00:00
if( bSteppedOnTapNote ) // this note is not judged and we stepped on its head
{
// Move the start of this Hold
//
// IMPORTANT: Every HoldNote::fStartBeat must be at least 1 index less than
// its HoldNote::fEndBeat. Otherwise, when HoldNotes are converted to the
// 4s representation, it disappears, which causes problems for the way we
// store HoldNote life (by index of the hold).
m_pNoteField->GetHoldNote(i).fStartBeat = min( fSongBeat, m_pNoteField->GetHoldNote(i).fEndBeat -NoteRowToBeat(1) );
}
if( bSteppedOnTapNote && bIsHoldingButton )
2001-12-19 01:50:57 +00:00
{
2002-06-14 22:25:22 +00:00
// Increase life
fLife = 1;
2002-06-14 22:25:22 +00:00
m_pNoteField->HoldNote( hn.iTrack ); // update the "electric ghost" effect
2001-12-19 01:50:57 +00:00
}
2002-08-01 03:15:27 +00:00
else
2001-12-19 01:50:57 +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
fLife -= fDeltaTime/PREFSMAN->m_fJudgeWindowOKSeconds;
2002-06-14 22:25:22 +00:00
fLife = max( fLife, 0 ); // clamp
// }
2001-12-19 01:50:57 +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
if( fSongBeat >= hn.fEndBeat && 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;
m_pNoteField->TapNote( 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 )
{
/* 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;
GAMESTATE->m_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;
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
}
m_pNoteField->SetHoldNoteLife(i, fLife); // update the NoteField display
m_pNoteField->SetHoldNoteScore(i, hns); // update the NoteField display
2002-12-18 22:25:24 +00:00
SetHoldNoteLife(i, fLife);
SetHoldNoteScore(i, hns);
2001-12-19 01:50:57 +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
2003-08-11 20:45:55 +00:00
* .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 );
}
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-08-18 16:57:00 +00:00
for( unsigned j=0; j<GAMESTATE->m_TransformsToApply[m_PlayerNumber].size(); j++ )
2003-08-18 02:37:43 +00:00
{
// Start beat needs to be far enough ahead to be off screen so that
// addition arrows don't suddenly pop on.
float fStartBeat = GAMESTATE->m_fSongBeat + BEATS_PER_MEASURE*2;
2003-08-18 17:19:34 +00:00
fStartBeat = truncf(fStartBeat)+1;
2003-08-18 02:37:43 +00:00
float fStartSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( fStartBeat );
float fEndSeconds = fStartSeconds+10;
float fEndBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fEndSeconds );
2003-08-18 17:19:34 +00:00
fEndBeat = truncf(fEndBeat)+1;
2003-08-18 02:37:43 +00:00
LOG->Trace( "Applying transform from %f to %f", fStartBeat, fEndBeat );
2003-08-18 16:57:00 +00:00
switch( GAMESTATE->m_TransformsToApply[m_PlayerNumber][j] )
2003-08-18 02:37:43 +00:00
{
case PlayerOptions::TRANSFORM_LITTLE:
NoteDataUtil::Little( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_WIDE:
NoteDataUtil::Wide( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_BIG:
NoteDataUtil::Big( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_QUICK:
NoteDataUtil::Quick( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_SKIPPY:
NoteDataUtil::Skippy( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_MINES:
NoteDataUtil::Mines( *this, fStartBeat, fEndBeat );
break;
case PlayerOptions::TRANSFORM_NONE:
default:
ASSERT(0);
}
m_pNoteField->CopyRange( this, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), BeatToNoteRow(fStartBeat)-1 );
2003-08-18 02:37:43 +00:00
}
GAMESTATE->m_TransformsToApply[m_PlayerNumber].clear();
2002-02-24 01:43:11 +00:00
ActorFrame::Update( fDeltaTime );
2001-11-03 10:52:42 +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.
2003-05-05 06:48:20 +00:00
if( GAMESTATE->GetCurrentStyleDef()->m_StyleType == StyleDef::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;
// Draw these below everything else.
m_ArrowBackdrop.Draw();
m_Combo.Draw();
2003-04-01 19:31:27 +00:00
float fTilt = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fPerspectiveTilt;
2003-08-17 00:15:54 +00:00
bool bReverse = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].GetReversePercentForColumn(0)>0.5;
2003-04-02 18:24:00 +00:00
float fReverseScale = bReverse ? -1.0f : 1.0f;
2003-04-01 19:31:27 +00:00
if( fTilt != 0 )
2002-02-24 10:31:20 +00:00
{
2002-11-15 08:02:27 +00:00
DISPLAY->EnterPerspective(45, false);
// construct view and project matrix
2003-04-02 04:41:49 +00:00
RageVector3 Up( 0.0f, 1.0f, 0.0f );
RageVector3 Eye( CENTER_X, CENTER_Y+SCALE(fTilt*fReverseScale,-1,1,-350,350), 500 );
2003-04-01 19:31:27 +00:00
// give a push the receptors toward the edge of the screen so they aren't so far in the middle
float fYOffset = SCALE(fTilt,-1,+1,10*fReverseScale,60*fReverseScale);
2003-04-02 04:41:49 +00:00
RageVector3 At( CENTER_X, CENTER_Y+fYOffset, 0 );
2002-11-15 08:02:27 +00:00
DISPLAY->LookAt(Eye, At, Up);
2002-02-24 10:31:20 +00:00
}
m_pNoteField->Draw();
2002-02-24 10:31:20 +00:00
2003-04-02 18:24:00 +00:00
if( fTilt != 0 )
2002-11-15 08:02:27 +00:00
DISPLAY->ExitPerspective();
2002-02-24 10:31:20 +00:00
2003-08-24 17:20:58 +00:00
if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fBlind == 0 )
2003-08-01 01:18:08 +00:00
{
if( GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_bProTiming )
m_ProTimingDisplay.Draw();
else
m_Judgment.Draw();
}
2002-02-24 10:31:20 +00:00
2003-02-01 05:16:38 +00:00
for( int c=0; c<GetNumTracks(); c++ )
2003-02-17 12:19:42 +00:00
m_HoldJudgment[c].Draw();
2001-12-28 10:15:59 +00:00
}
/* It's OK for this function to search a little more than was requested. */
int PlayerMinus::GetClosestNoteDirectional( int col, float fBeat, float fMaxBeatsDistance, int iDirection )
{
// look for the closest matching step
2003-02-09 03:33:36 +00:00
const int iIndexStartLookingAt = BeatToNoteRow( fBeat );
2001-11-03 10:52:42 +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++ )
{
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;
if( GetTapNote(col, iCurrentIndex) == TAP_EMPTY) continue; /* no note here */
if( GetTapNoteScore(col, iCurrentIndex) != TNS_NONE ) continue; /* this note has a score already */
2002-05-29 09:47:24 +00:00
2003-02-09 03:33:36 +00:00
return iCurrentIndex;
2002-05-29 09:47:24 +00:00
}
2003-02-09 03:33:36 +00:00
return -1;
}
int PlayerMinus::GetClosestNote( int col, float fBeat, float fMaxBeatsAhead, float fMaxBeatsBehind )
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
void PlayerMinus::Step( int col, RageTimer tm )
2003-02-09 02:07:30 +00:00
{
if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_BATTERY && GAMESTATE->m_CurStageStats.bFailed[m_PlayerNumber] ) // Oni dead
return; // do nothing
//LOG->Trace( "PlayerMinus::HandlePlayerStep()" );
2003-02-09 02:07:30 +00:00
ASSERT( col >= 0 && col <= GetNumTracks() );
2003-02-17 05:25:20 +00:00
int iIndexOverlappingNote = GetClosestNote( col, GAMESTATE->m_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 );
2003-04-13 00:44:50 +00:00
bool bGrayArrowStep = true;
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);
2003-02-17 05:25:20 +00:00
2003-07-13 00:34:30 +00:00
/* XXX: Double-check that we're handling m_fMusicRate correctly. */
/* We actually stepped on the note this long ago: */
const float fAgo = tm.Ago();
/* ... which means it happened at this point in the music: */
const float fMusicSeconds = GAMESTATE->m_fMusicSeconds - (fAgo / 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:
const float fNoteOffset = (fStepSeconds - fMusicSeconds) / GAMESTATE->m_SongOptions.m_fMusicRate; // account for music rate
2002-10-01 01:53:56 +00:00
const float fSecondsFromPerfect = fabsf( fNoteOffset );
2002-06-24 22:04:31 +00:00
TapNote tn = GetTapNote(col,iIndexOverlappingNote);
2003-04-07 21:24:14 +00:00
// calculate TapNoteScore
2002-12-17 22:07:59 +00:00
TapNoteScore score;
2002-06-24 22:04:31 +00:00
switch( GAMESTATE->m_PlayerController[m_PlayerNumber] )
2003-04-07 21:24:14 +00:00
{
2003-04-21 03:24:42 +00:00
case PC_HUMAN: {
if(GAMESTATE->m_CurGame == GAME_EZ2)
{
/* 1 is normal. 2 means scoring is half as hard; .5 means it's twice as hard. */
/* Ez2 is only perfect / good / miss */
float fScaledSecondsFromPerfect = fSecondsFromPerfect / PREFSMAN->m_fJudgeWindowScale;
if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowMarvelousSeconds ) score = TNS_PERFECT;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowPerfectSeconds ) score = TNS_PERFECT;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowGreatSeconds ) score = TNS_PERFECT;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowGoodSeconds ) score = TNS_GOOD;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowBooSeconds ) score = TNS_MISS;
else score = TNS_NONE;
}
else
{
/* 1 is normal. 2 means scoring is half as hard; .5 means it's twice as hard. */
float fScaledSecondsFromPerfect = fSecondsFromPerfect / PREFSMAN->m_fJudgeWindowScale;
if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowMarvelousSeconds ) score = TNS_MARVELOUS;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowPerfectSeconds ) score = TNS_PERFECT;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowGreatSeconds ) score = TNS_GREAT;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowGoodSeconds ) score = TNS_GOOD;
else if( fScaledSecondsFromPerfect <= PREFSMAN->m_fJudgeWindowBooSeconds ) score = TNS_BOO;
else score = TNS_NONE;
}
// Penalize for stepping on mines
if( tn == TAP_MINE && score > TNS_NONE )
{
m_soundMineExplosion.Play();
score = TNS_MISS;
m_pNoteField->TapMine( col, TNS_MISS );
}
break;
2003-04-21 03:24:42 +00:00
}
case PC_CPU:
score = PlayerAI::GetTapNoteScore( GAMESTATE->m_iCpuSkill[m_PlayerNumber], GAMESTATE->GetSumOfActiveAttackLevels(m_PlayerNumber) );
if( GAMESTATE->m_CurGame == GAME_EZ2 ) // scores are only perfect/good/miss on ez2 adjust accordingly
{
if(score == TNS_GREAT || score == TNS_MARVELOUS)
score = TNS_PERFECT;
if(score == TNS_BOO)
score = TNS_MISS;
}
/* 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
* AI generated a miss, and let UpdateMissedTapNotesOlderThan() detect and handle the
* misses. */
if( score == TNS_MISS )
return;
// Unless the computer made a very good step, they were fooled by the mine
if( tn == TAP_MINE && score <= TNS_GREAT )
{
m_soundMineExplosion.Play();
score = TNS_MISS;
m_pNoteField->TapMine( col, TNS_MISS );
}
break;
case PC_AUTOPLAY:
if(GAMESTATE->m_CurGame == GAME_EZ2)
{
score = TNS_PERFECT;
}
else
{
score = TNS_MARVELOUS;
}
// Don't step on mines
if( tn == TAP_MINE )
return;
break;
default:
ASSERT(0);
2003-04-22 04:54:04 +00:00
score = TNS_NONE;
break;
2003-04-07 21:24:14 +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 );
GAMESTATE->m_CurStageStats.iTotalError[m_PlayerNumber] += ms_error;
2003-08-01 01:18:08 +00:00
m_ProTimingDisplay.SetJudgment( ms_error, score );
2003-08-01 00:12:10 +00:00
}
if( score==TNS_MARVELOUS && !GAMESTATE->ShowMarvelous())
2002-08-27 16:53:25 +00:00
score = TNS_PERFECT;
2003-04-13 00:44:50 +00:00
bGrayArrowStep = score < TNS_GOOD;
2002-07-11 19:02:26 +00:00
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
if( GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_HUMAN &&
score >= TNS_GREAT )
2003-03-26 18:48:19 +00:00
HandleAutosync(fNoteOffset);
2002-06-24 22:04:31 +00:00
if( IsRowCompletelyJudged(iIndexOverlappingNote) )
OnRowCompletelyJudged( iIndexOverlappingNote );
}
2003-04-13 00:44:50 +00:00
if( bGrayArrowStep )
m_pNoteField->Step( col );
}
2001-11-03 10:52:42 +00:00
void PlayerMinus::HandleAutosync(float fNoteOffset)
2003-03-26 18:48:19 +00:00
{
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_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;
}
void PlayerMinus::OnRowCompletelyJudged( int iIndexThatWasSteppedOn )
{
2003-09-03 08:01:48 +00:00
// LOG->Trace( "PlayerMinus::OnRowCompletelyJudged" );
2002-06-24 22:04:31 +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 */
// TapNoteScore score = MinTapNoteScore(iIndexThatWasSteppedOn);
TapNoteScore score = LastTapNoteScore(iIndexThatWasSteppedOn);
2003-03-26 22:22:10 +00:00
ASSERT(score != TNS_NONE);
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);
if( tn == TAP_EMPTY ) continue; /* no note in this col */
if( tn == TAP_MINE ) continue; /* don't flash on mines b/c they're supposed to be missed */
// If the score is great or better, remove the note from the screen to
// indicate success.
if( score >= TNS_GREAT )
m_pNoteField->SetTapNote(c, iIndexThatWasSteppedOn, TAP_EMPTY);
// show the ghost arrow for this column
switch( score )
{
case TNS_GREAT:
case TNS_PERFECT:
case TNS_MARVELOUS:
2003-03-16 17:45:32 +00:00
{
bool bBright = GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber]>(int)BRIGHT_GHOST_COMBO_THRESHOLD;
m_pNoteField->TapNote( c, score, bBright );
2003-03-16 17:45:32 +00:00
}
break;
}
2002-10-24 05:24:56 +00:00
}
HandleTapRowScore( iIndexThatWasSteppedOn ); // update score
m_Combo.SetCombo( GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber] );
2002-05-19 01:59:48 +00:00
2003-02-17 12:19:42 +00:00
m_Judgment.SetJudgment( score );
2001-11-03 10:52:42 +00:00
}
void PlayerMinus::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
{
2003-08-03 00:13:55 +00:00
//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
2003-02-09 04:05:31 +00:00
const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds;
bool bFreeze;
float fMissIfOlderThanThisBeat;
float fThrowAway;
GAMESTATE->m_pCurSong->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze );
2001-11-20 11:49:10 +00:00
2002-05-19 01:59:48 +00:00
int iMissIfOlderThanThisIndex = BeatToNoteRow( fMissIfOlderThanThisBeat );
if( bFreeze )
{
/* iMissIfOlderThanThisIndex is a freeze. Include the index of the freeze,
* too. Otherwise we won't show misses for tap notes on freezes until the
* freeze finishes. */
iMissIfOlderThanThisIndex++;
}
2001-11-20 11:49:10 +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.
2002-02-24 01:43:11 +00:00
// Instead, only check 10 elements back. Even 10 is overkill.
int iStartCheckingAt = max( 0, iMissIfOlderThanThisIndex-10 );
2001-12-19 01:50:57 +00:00
//LOG->Trace( "iStartCheckingAt: %d iMissIfOlderThanThisIndex: %d", iStartCheckingAt, iMissIfOlderThanThisIndex );
2003-02-09 04:05:31 +00:00
int iNumMissesFound = 0;
2003-08-10 19:07:54 +00:00
int iNumMinesMissed = 0;
for( int r=iStartCheckingAt; r<iMissIfOlderThanThisIndex; r++ )
2001-12-19 01:50:57 +00:00
{
bool MissedNoteOnThisRow = false;
2003-08-10 19:07:54 +00:00
bool MissedMineOnThisRow = false;
2003-02-01 05:16:38 +00:00
for( int t=0; t<GetNumTracks(); t++ )
{
if( GetTapNote(t, r) == TAP_EMPTY) continue; /* no note here */
2003-08-10 19:07:54 +00:00
if( GetTapNoteScore(t, r) != TNS_NONE ) continue; /* note here is already hit */
if( GetTapNote(t, r) == TAP_MINE )
{
// A mine. Reward for not stepping on it.
MissedMineOnThisRow = true;
SetTapNoteScore(t, r, GAMESTATE->ShowMarvelous()? TNS_MARVELOUS:TNS_PERFECT);
2003-08-10 19:07:54 +00:00
}
else
{
// A normal note. Penalize for not stepping on it.
MissedNoteOnThisRow = true;
SetTapNoteScore(t, r, TNS_MISS);
GAMESTATE->m_CurStageStats.iTotalError[m_PlayerNumber] += MAX_PRO_TIMING_ERROR;
m_ProTimingDisplay.SetJudgment( MAX_PRO_TIMING_ERROR, TNS_MISS );
}
}
2003-08-10 19:07:54 +00:00
if( MissedNoteOnThisRow )
{
iNumMissesFound++;
HandleTapRowScore( r );
m_Combo.SetCombo( GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber] );
}
else if( MissedMineOnThisRow )
{
iNumMinesMissed++;
HandleTapRowScore( r );
m_Combo.SetCombo( GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber] );
}
2001-12-19 01:50:57 +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 );
2003-08-10 19:07:54 +00:00
else if( iNumMinesMissed > 0 )
m_Judgment.SetJudgment( GAMESTATE->ShowMarvelous()? TNS_MARVELOUS:TNS_PERFECT );
}
void PlayerMinus::CrossedRow( int iNoteRow )
2002-06-24 22:04:31 +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++ )
if( GetTapNote(t, iNoteRow) != TAP_EMPTY )
if( GetTapNoteScore(t, iNoteRow) == TNS_NONE )
Step( t, now );
2003-07-13 00:34:30 +00:00
}
// Hold the panel while crossing a mine will cause the mine to explode
for( int t=0; t<GetNumTracks(); t++ )
{
if( GetTapNote(t, iNoteRow) == TAP_MINE )
{
const StyleInput StyleI( m_PlayerNumber, t );
const GameInput GameI = GAMESTATE->GetCurrentStyleDef()->StyleInputToGameInput( StyleI );
bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI );
if( bIsHoldingButton )
Step( t, now );
}
}
2002-06-24 22:04:31 +00:00
}
void PlayerMinus::RandomiseNotes( int iNoteRow )
{
2003-05-11 08:19:24 +00:00
int NewNoteRow = (int)(iNoteRow + 50 / GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fScrollSpeed); // change the row to look ahead from based upon their speed mod
// check to see if they're at the crossed row
2003-05-11 08:19:24 +00:00
// int EmptyNoteCol = -1;
// int WaitingForEmptyColumn = -1;
bool b_updatenotedata = false;
int iNumOfTracks = GetNumTracks();
for(int t=0; t<iNumOfTracks; t++)
{
if(t+1 < iNumOfTracks)
{
int iRandomTrackToSwapWith = RandomInt(0, iNumOfTracks-1);
TapNote t1 = GetTapNote(t, NewNoteRow);
TapNote t2 = GetTapNote(iRandomTrackToSwapWith, NewNoteRow);
if((t1 == TAP_TAP || t1 == TAP_EMPTY) && (t2 == TAP_TAP || t2 == TAP_EMPTY) && (!(t1 == TAP_EMPTY && t2 == TAP_EMPTY) && !(t1 == TAP_TAP && t2 == TAP_TAP)))
{
SetTapNote(t, NewNoteRow, t2);
SetTapNote(iRandomTrackToSwapWith, NewNoteRow, t1);
b_updatenotedata = true;
}
}
}
if(b_updatenotedata)
{
m_pNoteField->CopyAll((NoteData*)this);
}
}
2002-06-24 22:04:31 +00:00
void PlayerMinus::HandleTapRowScore( unsigned row )
2002-06-24 22:04:31 +00:00
{
TapNoteScore scoreOfLastTap = LastTapNoteScore(row);
int iNumTapsInRow = 0;
int iNumAdditions = 0;
for( int t=0; t<GetNumTracks(); t++ ) // for each column
{
TapNote tn = GetTapNote(t, row);
if( tn != TAP_EMPTY )
iNumTapsInRow++;
if( tn == TAP_ADDITION )
iNumAdditions++;
}
ASSERT(iNumTapsInRow > 0);
bool NoCheating = true;
2003-04-22 04:54:04 +00:00
#ifdef DEBUG
NoCheating = false;
#endif
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 )
return;
/* Update miss combo, and handle "combo stopped" messages. */
int &iCurCombo = GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber];
switch( scoreOfLastTap )
{
case TNS_MARVELOUS:
case TNS_PERFECT:
case TNS_GREAT:
GAMESTATE->m_CurStageStats.iCurMissCombo[m_PlayerNumber] = 0;
SCREENMAN->PostMessageToTopScreen( SM_MissComboAborted, 0 );
break;
case TNS_MISS:
++GAMESTATE->m_CurStageStats.iCurMissCombo[m_PlayerNumber];
case TNS_GOOD:
case TNS_BOO:
if( iCurCombo > 50 )
SCREENMAN->PostMessageToTopScreen( SM_ComboStopped, 0 );
iCurCombo = 0;
break;
}
/* The score keeper updates the hit combo. Remember the old combo for handling announcers. */
const int iOldCombo = GAMESTATE->m_CurStageStats.iCurCombo[m_PlayerNumber];
if(m_pPrimaryScoreKeeper)
m_pPrimaryScoreKeeper->HandleTapRowScore(scoreOfLastTap, iNumTapsInRow, iNumAdditions );
if(m_pSecondaryScoreKeeper)
m_pSecondaryScoreKeeper->HandleTapRowScore(scoreOfLastTap, iNumTapsInRow, iNumAdditions );
#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
// new max combo
GAMESTATE->m_CurStageStats.iMaxCombo[m_PlayerNumber] = max(GAMESTATE->m_CurStageStats.iMaxCombo[m_PlayerNumber], iCurCombo);
if (m_pScore)
2003-06-18 20:08:39 +00:00
m_pScore->SetScore(GAMESTATE->m_CurStageStats.iScore[m_PlayerNumber]);
if( m_pLifeMeter ) {
2003-03-16 17:45:32 +00:00
m_pLifeMeter->ChangeLife( scoreOfLastTap );
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
void PlayerMinus::HandleHoldScore( HoldNoteScore holdScore, TapNoteScore tapScore )
2002-07-28 20:28:37 +00:00
{
bool NoCheating = true;
2003-06-18 04:25:49 +00:00
#ifdef DEBUG
NoCheating = false;
#endif
if(GAMESTATE->m_bDemonstrationOrJukebox)
NoCheating = false;
// don't accumulate points if AutoPlay is on.
if( NoCheating && GAMESTATE->m_PlayerController[m_PlayerNumber] == PC_AUTOPLAY )
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-11-03 20:52:13 +00:00
if (m_pScore)
2003-06-18 20:08:39 +00:00
m_pScore->SetScore(GAMESTATE->m_CurStageStats.iScore[m_PlayerNumber]);
2002-11-03 20:52:13 +00:00
if( m_pLifeMeter ) {
2003-03-16 17:45:32 +00:00
m_pLifeMeter->ChangeLife( holdScore, tapScore );
m_pLifeMeter->OnDancePointsChange();
}
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
float PlayerMinus::GetMaxStepDistanceSeconds()
2002-07-28 20:28:37 +00:00
{
2003-02-09 04:05:31 +00:00
return GAMESTATE->m_SongOptions.m_fMusicRate * PREFSMAN->m_fJudgeWindowBooSeconds * PREFSMAN->m_fJudgeWindowScale;
2002-09-05 03:45:07 +00:00
}
void PlayerMinus::FadeToFail()
2002-09-05 03:45:07 +00:00
{
m_pNoteField->FadeToFail();
}
void Player::Load( PlayerNumber player_no, NoteData* pNoteData, LifeMeter* pLM, CombinedLifeMeter* pCombinedLM, ScoreDisplay* pScore, Inventory* pInventory, ScoreKeeper* pPrimaryScoreKeeper, ScoreKeeper* pSecondaryScoreKeeper )
{
PlayerMinus::Load( player_no, pNoteData, pLM, pCombinedLM, pScore, pInventory, pPrimaryScoreKeeper, pSecondaryScoreKeeper, &m_NoteField );
2002-09-05 03:45:07 +00:00
}