diff --git a/stepmania/Themes/default/metrics.ini b/stepmania/Themes/default/metrics.ini index e3e6cd1784..c32087517e 100644 --- a/stepmania/Themes/default/metrics.ini +++ b/stepmania/Themes/default/metrics.ini @@ -2349,6 +2349,9 @@ NumChambers=32 NumStrips=2 InitialValue=0.5 +[LifeMeterTime] +Fallback=LifeMeterBar + [DifficultyMeter] NumFeetInMeter=10 MaxFeetInMeter=14 diff --git a/stepmania/src/LifeMeter.h b/stepmania/src/LifeMeter.h index e4dd2adcf6..8ff82a88bd 100644 --- a/stepmania/src/LifeMeter.h +++ b/stepmania/src/LifeMeter.h @@ -25,9 +25,9 @@ public: virtual bool IsInDanger() const = 0; virtual bool IsHot() const = 0; virtual bool IsFailing() const = 0; - virtual float GetLife() const { return 0; } // for cosmetic use only virtual void UpdateNonstopLifebar(int cleared, int total, int ProgressiveLifebarDifficulty) = 0; + virtual void ForceFail() = 0; protected: PlayerNumber m_PlayerNumber; diff --git a/stepmania/src/LifeMeterBar.cpp b/stepmania/src/LifeMeterBar.cpp index f30e215666..fb8a1d2593 100644 --- a/stepmania/src/LifeMeterBar.cpp +++ b/stepmania/src/LifeMeterBar.cpp @@ -12,6 +12,7 @@ #include "PlayerState.h" #include "Quad.h" #include "ActorUtil.h" +#include "StreamDisplay.h" static ThemeMetric METER_WIDTH ("LifeMeterBar","MeterWidth"); @@ -21,201 +22,9 @@ static ThemeMetric NUM_CHAMBERS ("LifeMeterBar","NumChambers"); static ThemeMetric NUM_STRIPS ("LifeMeterBar","NumStrips"); static ThemeMetric INITIAL_VALUE ("LifeMeterBar","InitialValue"); - const float FAIL_THRESHOLD = 0; -class LifeMeterStream : public Actor -{ -public: - LifeMeterStream() - { - bool bExtra = GAMESTATE->IsExtraStage()||GAMESTATE->IsExtraStage2(); - - m_quadMask.SetDiffuse( RageColor(0,0,0,1) ); - m_quadMask.SetZ( 1 ); - m_quadMask.SetBlendMode( BLEND_NO_EFFECT ); - m_quadMask.SetUseZBuffer( true ); - - CString sGraphicPath; - RageTextureID ID; - ID.bStretch = true; - - ID.filename = THEME->GetPathG("LifeMeterBar",ssprintf("%snormal", bExtra?"extra ":"")); - m_sprStreamNormal.Load( ID ); - m_sprStreamNormal.SetUseZBuffer( true ); - - ID.filename = THEME->GetPathG("LifeMeterBar",ssprintf("%shot", bExtra?"extra ":"")); - m_sprStreamHot.Load( ID ); - m_sprStreamHot.SetUseZBuffer( true ); - - ID.filename = THEME->GetPathG("LifeMeterBar",ssprintf("%spassing", bExtra?"extra ":"")); - m_sprStreamPassing.Load( ID ); - m_sprStreamPassing.SetUseZBuffer( true ); - - ID.filename = THEME->GetPathG("LifeMeterBar",ssprintf("%sframe", bExtra?"extra ":"")); - m_sprFrame.Load( ID ); - } - - Sprite m_sprStreamNormal; - Sprite m_sprStreamHot; - Sprite m_sprStreamPassing; - Sprite m_sprFrame; - Quad m_quadMask; - - PlayerNumber m_PlayerNumber; - float m_fPercent; - float m_fPassingAlpha; - float m_fHotAlpha; - - void GetChamberIndexAndOverslow( float fPercent, int& iChamberOut, float& fChamberOverflowPercentOut ) - { - iChamberOut = (int)(fPercent*NUM_CHAMBERS); - fChamberOverflowPercentOut = fPercent*NUM_CHAMBERS - iChamberOut; - } - - float GetChamberLeftPercent( int iChamber ) - { - return (iChamber+0) / (float)NUM_CHAMBERS; - } - - float GetChamberRightPercent( int iChamber ) - { - return (iChamber+1) / (float)NUM_CHAMBERS; - } - - float GetRightEdgePercent( int iChamber, float fChamberOverflowPercent ) - { - if( (iChamber%2) == 0 ) - return (iChamber+fChamberOverflowPercent) / (float)NUM_CHAMBERS; - else - return (iChamber+1) / (float)NUM_CHAMBERS; - } - - float GetHeightPercent( int iChamber, float fChamberOverflowPercent ) - { - if( (iChamber%2) == 1 ) - return 1-fChamberOverflowPercent; - else - return 0; - } - - void DrawPrimitives() - { - if( GAMESTATE->IsPlayerEnabled(m_PlayerNumber) ) - { - DrawMask( m_fPercent ); // this is the "right endcap" to the life - - const float fChamberWidthInPercent = 1.0f/NUM_CHAMBERS; - float fPercentBetweenStrips = 1.0f/NUM_STRIPS; - // round this so that the chamber overflows align - if( NUM_CHAMBERS > 10 ) - fPercentBetweenStrips = Quantize( fPercentBetweenStrips, fChamberWidthInPercent ); - - float fPercentOffset = fmodf( GAMESTATE->m_fSongBeat/4+1000, fPercentBetweenStrips ); - ASSERT( fPercentOffset >= 0 && fPercentOffset <= fPercentBetweenStrips ); - - for( float f=fPercentOffset+1; f>=0; f-=fPercentBetweenStrips ) - { - DrawMask( f ); - DrawStrip( f ); - } - - } - - m_sprFrame.Draw(); - - } - - void DrawStrip( float fRightEdgePercent ) - { - RectF rect; - - const float fChamberWidthInPercent = 1.0f/NUM_CHAMBERS; - const float fStripWidthInPercent = 1.0f/NUM_STRIPS; - - const float fCorrectedRightEdgePercent = fRightEdgePercent + fChamberWidthInPercent; - const float fCorrectedStripWidthInPercent = fStripWidthInPercent + 2*fChamberWidthInPercent; - const float fCorrectedLeftEdgePercent = fCorrectedRightEdgePercent - fCorrectedStripWidthInPercent; - - - // set size of streams - rect.left = -METER_WIDTH/2 + METER_WIDTH*max(0,fCorrectedLeftEdgePercent); - rect.top = -METER_HEIGHT/2; - rect.right = -METER_WIDTH/2 + METER_WIDTH*min(1,fCorrectedRightEdgePercent); - rect.bottom = +METER_HEIGHT/2; - - ASSERT( rect.left <= METER_WIDTH/2 && rect.right <= METER_WIDTH/2 ); - - float fPercentCroppedFromLeft = max( 0, -fCorrectedLeftEdgePercent ); - float fPercentCroppedFromRight = max( 0, fCorrectedRightEdgePercent-1 ); - - - m_sprStreamNormal.StretchTo( rect ); - m_sprStreamPassing.StretchTo( rect ); - m_sprStreamHot.StretchTo( rect ); - - - // set custom texture coords -// float fPrecentOffset = fRightEdgePercent; - - RectF frectCustomTexRect( - fPercentCroppedFromLeft, - 0, - 1-fPercentCroppedFromRight, - 1); - - m_sprStreamNormal.SetCustomTextureRect( frectCustomTexRect ); - m_sprStreamPassing.SetCustomTextureRect( frectCustomTexRect ); - m_sprStreamHot.SetCustomTextureRect( frectCustomTexRect ); - - m_sprStreamPassing.SetDiffuse( RageColor(1,1,1,m_fPassingAlpha) ); - m_sprStreamHot.SetDiffuse( RageColor(1,1,1,m_fHotAlpha) ); - - m_sprStreamNormal.Draw(); - m_sprStreamPassing.Draw(); - m_sprStreamHot.Draw(); - } - - void DrawMask( float fPercent ) - { - RectF rect; - - int iChamber; - float fChamberOverflowPercent; - GetChamberIndexAndOverslow( fPercent, iChamber, fChamberOverflowPercent ); - float fRightPercent = GetRightEdgePercent( iChamber, fChamberOverflowPercent ); - float fHeightPercent = GetHeightPercent( iChamber, fChamberOverflowPercent ); - float fChamberLeftPercent = GetChamberLeftPercent( iChamber ); - float fChamberRightPercent = GetChamberRightPercent( iChamber ); - - // draw mask for vertical chambers - rect.left = -METER_WIDTH/2 + fChamberLeftPercent*METER_WIDTH-1; - rect.top = -METER_HEIGHT/2; - rect.right = -METER_WIDTH/2 + fChamberRightPercent*METER_WIDTH+1; - rect.bottom = -METER_HEIGHT/2 + fHeightPercent*METER_HEIGHT; - - rect.left = MIN( rect.left, + METER_WIDTH/2 ); - rect.right = MIN( rect.right, + METER_WIDTH/2 ); - - m_quadMask.StretchTo( rect ); - m_quadMask.Draw(); - - // draw mask for horizontal chambers - rect.left = -METER_WIDTH/2 + fRightPercent*METER_WIDTH; - rect.top = -METER_HEIGHT/2; - rect.right = +METER_WIDTH/2; - rect.bottom = +METER_HEIGHT/2; - - rect.left = MIN( rect.left, + METER_WIDTH/2 ); - rect.right = MIN( rect.right, + METER_WIDTH/2 ); - - m_quadMask.StretchTo( rect ); - m_quadMask.Draw(); - } -}; - - LifeMeterBar::LifeMeterBar() { switch( GAMESTATE->m_SongOptions.m_DrainType ) @@ -231,8 +40,8 @@ LifeMeterBar::LifeMeterBar() default: ASSERT(0); } - m_fTrailingLifePercentage = 0; - m_fLifeVelocity = 0; + const CString sType = "LifeMeterBar"; + m_fPassingAlpha = 0; m_fHotAlpha = 0; m_bFailedEarlier = false; @@ -247,7 +56,7 @@ LifeMeterBar::LifeMeterBar() // set up combotoregainlife m_iComboToRegainLife = 0; - m_sprBackground.Load( THEME->GetPathG("LifeMeterBar","background") ); + m_sprBackground.Load( THEME->GetPathG(sType,"background") ); m_sprBackground->SetName( "Background" ); m_sprBackground->ZoomToWidth( METER_WIDTH ); m_sprBackground->ZoomToHeight( METER_HEIGHT ); @@ -255,26 +64,42 @@ LifeMeterBar::LifeMeterBar() m_quadDangerGlow.ZoomToWidth( METER_WIDTH ); m_quadDangerGlow.ZoomToHeight( METER_HEIGHT ); - m_quadDangerGlow.SetDiffuse( RageColor(1,0,0,1) ); + m_quadDangerGlow.SetEffectDiffuseShift(); + m_quadDangerGlow.SetEffectColor1( RageColor(1,0,0,0.8f) ); + m_quadDangerGlow.SetEffectColor2( RageColor(1,0,0,0) ); + m_quadDangerGlow.SetEffectClock( Actor::CLOCK_BGM_BEAT ); this->AddChild( &m_quadDangerGlow ); - m_pStream = new LifeMeterStream; + m_pStream = new StreamDisplay; + bool bExtra = GAMESTATE->IsExtraStage()||GAMESTATE->IsExtraStage2(); + CString sExtra = bExtra ? "extra " : ""; + m_pStream->Load( + METER_WIDTH, + METER_HEIGHT, + NUM_STRIPS, + NUM_CHAMBERS, + THEME->GetPathG(sType,sExtra+"normal"), + THEME->GetPathG(sType,sExtra+"hot"), + THEME->GetPathG(sType,sExtra+"passing") + ); this->AddChild( m_pStream ); + m_sprFrame.Load( THEME->GetPathG(sType,sExtra+"frame") ); + m_sprFrame->SetName( "Frame" ); + this->AddChild( m_sprFrame ); + AfterLifeChanged(); } LifeMeterBar::~LifeMeterBar() { - delete m_pStream; + SAFE_DELETE( m_pStream ); } void LifeMeterBar::Load( PlayerNumber pn ) { LifeMeter::Load( pn ); - m_pStream->m_PlayerNumber = pn; - if( pn == PLAYER_2 ) m_pStream->SetZoomX( -1 ); } @@ -435,16 +260,15 @@ void LifeMeterBar::ChangeLife( float fDeltaLife ) m_fLifePercentage += fDeltaLife; CLAMP( m_fLifePercentage, 0, 1 ); + AfterLifeChanged(); if( m_fLifePercentage <= FAIL_THRESHOLD ) STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].bFailedEarlier = true; - - m_fLifeVelocity += fDeltaLife; } void LifeMeterBar::AfterLifeChanged() { - + m_pStream->SetPercent( m_fLifePercentage ); } bool LifeMeterBar::IsPastPassmark() const @@ -478,37 +302,6 @@ bool LifeMeterBar::IsFailing() const void LifeMeterBar::Update( float fDeltaTime ) { LifeMeter::Update( fDeltaTime ); - - - // HACK: Tweaking these values is very difficulty. Update the - // "physics" many times so that the spring motion appears faster - for( int i=0; i<10; i++ ) - { - const float fDelta = m_fLifePercentage - m_fTrailingLifePercentage; - - // Don't apply spring and viscous forces if we're full or empty. - // Just move straight to either full or empty. - if( IsFailing() || IsHot() ) - { - m_fLifeVelocity = (fDelta / fabsf(fDelta)) * 4; - } - else - { - const float fSpringForce = fDelta * 2.0f; - m_fLifeVelocity += fSpringForce * fDeltaTime; - - const float fViscousForce = -m_fLifeVelocity * 0.2f; - m_fLifeVelocity += fViscousForce * fDeltaTime; - } - - CLAMP( m_fLifeVelocity, -.06f, +.02f ); - - m_fTrailingLifePercentage += m_fLifeVelocity * fDeltaTime; - } - - // Don't clamp life percentage a little outside the visible range so - // that the clamp doesn't dampen the "jiggle" of the meter. - CLAMP( m_fTrailingLifePercentage, -0.1f, 1.1f ); m_fPassingAlpha += IsPastPassmark() ? +fDeltaTime*2 : -fDeltaTime*2; CLAMP( m_fPassingAlpha, 0, 1 ); @@ -516,24 +309,18 @@ void LifeMeterBar::Update( float fDeltaTime ) m_fHotAlpha += IsHot() ? + fDeltaTime*2 : -fDeltaTime*2; CLAMP( m_fHotAlpha, 0, 1 ); - if( IsHot() ) - m_fLifeVelocity = max( 0, m_fLifeVelocity ); + m_pStream->SetPassingAlpha( m_fPassingAlpha ); + m_pStream->SetHotAlpha( m_fHotAlpha ); + + if( m_pStream->GetTrailingLifePercent() < DANGER_THRESHOLD && !GAMESTATE->IsPlayerDead(m_PlayerNumber) ) + m_quadDangerGlow.SetDiffuseAlpha( 1 ); + else + m_quadDangerGlow.SetDiffuseAlpha( 0 ); } void LifeMeterBar::DrawPrimitives() { - m_pStream->m_fPercent = m_fTrailingLifePercentage; - m_pStream->m_fPassingAlpha = m_fPassingAlpha; - m_pStream->m_fHotAlpha = m_fHotAlpha; - - float fPercentRed = 0; - if( GAMESTATE->IsPlayerDead(m_PlayerNumber) ) - fPercentRed = 0; - else if( m_fTrailingLifePercentagem_SongOptions.m_iBatteryLives; } +void LifeMeterBattery::ForceFail() +{ + m_iLivesLeft = 0; + Refresh(); +} + void LifeMeterBattery::Refresh() { if( m_iLivesLeft <= 4 ) diff --git a/stepmania/src/LifeMeterBattery.h b/stepmania/src/LifeMeterBattery.h index 20f755cc92..1df182142b 100644 --- a/stepmania/src/LifeMeterBattery.h +++ b/stepmania/src/LifeMeterBattery.h @@ -29,6 +29,7 @@ public: virtual bool IsFailing() const; virtual float GetLife() const; virtual void UpdateNonstopLifebar(int cleared, int total, int ProgressiveLifebarDifficulty) { }; + virtual void ForceFail(); void Refresh(); diff --git a/stepmania/src/LifeMeterTime.cpp b/stepmania/src/LifeMeterTime.cpp index 32ccce9a4d..d5f34bee96 100644 --- a/stepmania/src/LifeMeterTime.cpp +++ b/stepmania/src/LifeMeterTime.cpp @@ -7,12 +7,21 @@ #include "ActorUtil.h" #include "Course.h" #include "PrefsManager.h" +#include "StreamDisplay.h" -const float MAX_LIFE_SECONDS = 3*60; +const float FULL_LIFE_SECONDS = 3*60; + +static ThemeMetric METER_WIDTH ("LifeMeterTime","MeterWidth"); +static ThemeMetric METER_HEIGHT ("LifeMeterTime","MeterHeight"); +static ThemeMetric DANGER_THRESHOLD ("LifeMeterTime","DangerThreshold"); +static ThemeMetric NUM_CHAMBERS ("LifeMeterTime","NumChambers"); +static ThemeMetric NUM_STRIPS ("LifeMeterTime","NumStrips"); +static ThemeMetric INITIAL_VALUE ("LifeMeterTime","InitialValue"); LifeMeterTime::LifeMeterTime() { - m_fLifeRemainingSeconds = 0; + m_fLifeTotalGainedSeconds = 0; + m_fLifeTotalLostSeconds = 0; } void LifeMeterTime::Load( PlayerNumber pn ) @@ -23,26 +32,42 @@ void LifeMeterTime::Load( PlayerNumber pn ) bool bPlayerEnabled = GAMESTATE->IsPlayerEnabled(pn); - m_sprFrame.Load( THEME->GetPathG(sType,"frame") ); + m_sprBackground.Load( THEME->GetPathG(sType,"background") ); + m_sprBackground->SetName( "Background" ); + m_sprBackground->ZoomToWidth( METER_WIDTH ); + m_sprBackground->ZoomToHeight( METER_HEIGHT ); + this->AddChild( m_sprBackground ); + + m_quadDangerGlow.ZoomToWidth( METER_WIDTH ); + m_quadDangerGlow.ZoomToHeight( METER_HEIGHT ); + m_quadDangerGlow.SetEffectDiffuseShift(); + m_quadDangerGlow.SetEffectColor1( RageColor(1,0,0,0.8f) ); + m_quadDangerGlow.SetEffectColor2( RageColor(1,0,0,0) ); + m_quadDangerGlow.SetEffectClock( Actor::CLOCK_BGM_BEAT ); + this->AddChild( &m_quadDangerGlow ); + + m_pStream = new StreamDisplay; + bool bExtra = GAMESTATE->IsExtraStage()||GAMESTATE->IsExtraStage2(); + CString sExtra = bExtra ? "extra " : ""; + m_pStream->Load( + METER_WIDTH, + METER_HEIGHT, + NUM_STRIPS, + NUM_CHAMBERS, + THEME->GetPathG(sType,sExtra+"normal"), + THEME->GetPathG(sType,sExtra+"hot"), + THEME->GetPathG(sType,sExtra+"passing") + ); + this->AddChild( m_pStream ); + + m_sprFrame.Load( THEME->GetPathG(sType,sExtra+"frame") ); m_sprFrame->SetName( "Frame" ); this->AddChild( m_sprFrame ); - ActorUtil::OnCommand( m_sprFrame, sType ); - - m_textTimeRemaining.LoadFromFont( THEME->GetPathF(sType, "TimeRemaining") ); - m_textTimeRemaining.SetName( "TimeRemaining" ); - this->AddChild( &m_textTimeRemaining ); - ActorUtil::OnCommand( m_textTimeRemaining, sType ); - - m_Meter.SetName( "Meter" ); - m_Meter.Load( THEME->GetPathG(sType,"Stream"), THEME->GetMetricF(sType,"StreamWidth"), THEME->GetPathG(sType,"Tip") ); - this->AddChild( &m_Meter ); - ActorUtil::OnCommand( m_Meter, sType ); - - FOREACH_TapNoteScore( tns ) - m_TapCommands[tns] = THEME->GetMetricA( sType, TapNoteScoreToString(tns)+"Command" ); - m_GainLife = THEME->GetMetricA( sType, "GainLifeCommand" ); m_soundGainLife.Load( THEME->GetPathS(sType,"GainLife") ); + + if( pn == PLAYER_2 ) + m_pStream->SetZoomX( -1 ); } void LifeMeterTime::OnLoadSong() @@ -53,54 +78,54 @@ void LifeMeterTime::OnLoadSong() Course* pCourse = GAMESTATE->m_pCurCourse; ASSERT( pCourse ); const CourseEntry *pEntry = &pCourse->m_entries[GAMESTATE->GetCourseSongIndex()]; - m_fLifeRemainingSeconds += pEntry->fGainSeconds; - CLAMP( m_fLifeRemainingSeconds, 0, MAX_LIFE_SECONDS ); + m_fLifeTotalGainedSeconds += pEntry->fGainSeconds; - m_soundGainLife.Play(); - - m_textTimeRemaining.RunCommands( m_GainLife ); + if( GAMESTATE->GetCourseSongIndex() > 0 ) + m_soundGainLife.Play(); } -void LifeMeterTime::ChangeLife( TapNoteScore score ) +void LifeMeterTime::ChangeLife( TapNoteScore tns ) { if( STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].bFailedEarlier ) return; - switch( score ) + float fMeterChange = 0; + switch( tns ) { - case TNS_MARVELOUS: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangeMarvelous; break; - case TNS_PERFECT: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangePerfect; break; - case TNS_GREAT: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangeGreat; break; - case TNS_GOOD: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangeGood; break; - case TNS_BOO: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangeBoo; break; - case TNS_MISS: m_fLifeRemainingSeconds += PREFSMAN->m_fTimeMeterSecondsChangeMiss; break; + case TNS_MARVELOUS: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeMarvelous; break; + case TNS_PERFECT: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangePerfect; break; + case TNS_GREAT: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeGreat; break; + case TNS_GOOD: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeGood; break; + case TNS_BOO: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeBoo; break; + case TNS_MISS: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeMiss; break; + case TNS_HIT_MINE: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeHitMine; break; default: ASSERT(0); } - CLAMP( m_fLifeRemainingSeconds, 0, MAX_LIFE_SECONDS ); - m_textTimeRemaining.RunCommands( m_TapCommands[score] ); + m_fLifeTotalLostSeconds -= fMeterChange; - if( m_fLifeRemainingSeconds == 0 ) + if( GetLifeSeconds() <= 0 ) STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].bFailedEarlier = true; } -void LifeMeterTime::ChangeLife( HoldNoteScore score, TapNoteScore tscore ) +void LifeMeterTime::ChangeLife( HoldNoteScore hns, TapNoteScore tns ) { - switch( score ) - { - case HNS_OK: - break; - case HNS_NG: - ChangeLife( TNS_MISS ); // NG is the same as a miss - break; - default: - ASSERT(0); - } -} + if( STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].bFailedEarlier ) + return; -void LifeMeterTime::ChangeLife( float fDeltaLifePercent ) -{ + float fMeterChange = 0; + switch( hns ) + { + case HNS_OK: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeOK; break; + case HNS_NG: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeNG; break; + default: ASSERT(0); + } + + m_fLifeTotalLostSeconds -= fMeterChange; + + if( GetLifeSeconds() <= 0 ) + STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].bFailedEarlier = true; } void LifeMeterTime::OnDancePointsChange() @@ -125,17 +150,43 @@ bool LifeMeterTime::IsFailing() const void LifeMeterTime::Update( float fDeltaTime ) { - LifeMeter::Update( fDeltaTime ); + // update current stage stats so ScoreDisplayLifeTime can show the right thing + float fSecs = GetLifeSeconds(); + fSecs = max( 0, fSecs ); + STATSMAN->m_CurStageStats.m_player[m_PlayerNumber].fLifeRemainingSeconds = fSecs; - m_textTimeRemaining.SetText( SecondsToMMSSMsMs(m_fLifeRemainingSeconds) ); - m_Meter.SetPercent( m_fLifeRemainingSeconds/MAX_LIFE_SECONDS ); + LifeMeter::Update( fDeltaTime ); + + // TODO + m_pStream->SetPercent( GetLife() ); + m_pStream->SetPassingAlpha( 0 ); + m_pStream->SetHotAlpha( 0 ); + + if( m_pStream->GetTrailingLifePercent() < DANGER_THRESHOLD && !GAMESTATE->IsPlayerDead(m_PlayerNumber) ) + m_quadDangerGlow.SetDiffuseAlpha( 1 ); + else + m_quadDangerGlow.SetDiffuseAlpha( 0 ); } float LifeMeterTime::GetLife() const { - return m_fLifeRemainingSeconds/MAX_LIFE_SECONDS; + float fPercent = GetLifeSeconds() / FULL_LIFE_SECONDS; + CLAMP( fPercent, 0, 1 ); + return fPercent; } +void LifeMeterTime::ForceFail() +{ + m_fLifeTotalLostSeconds = m_fLifeTotalGainedSeconds + 100; +} + +float LifeMeterTime::GetLifeSeconds() const +{ + float fSecs = m_fLifeTotalGainedSeconds - (m_fLifeTotalLostSeconds + STATSMAN->m_CurStageStats.fGameplaySeconds); + return fSecs; +} + + /* * (c) 2001-2004 Chris Danford * All rights reserved. diff --git a/stepmania/src/LifeMeterTime.h b/stepmania/src/LifeMeterTime.h index 2a2ab2d5ee..1e024b30a4 100644 --- a/stepmania/src/LifeMeterTime.h +++ b/stepmania/src/LifeMeterTime.h @@ -10,6 +10,8 @@ #include "PercentageDisplay.h" #include "AutoActor.h" #include "MeterDisplay.h" +#include "Quad.h" +class StreamDisplay; class LifeMeterTime : public LifeMeter { @@ -23,22 +25,24 @@ public: virtual void OnLoadSong(); virtual void ChangeLife( TapNoteScore score ); virtual void ChangeLife( HoldNoteScore score, TapNoteScore tscore ); - virtual void ChangeLife( float fDeltaLifePercent ); virtual void OnDancePointsChange(); // look in GAMESTATE and update the display virtual bool IsInDanger() const; virtual bool IsHot() const; virtual bool IsFailing() const; virtual float GetLife() const; virtual void UpdateNonstopLifebar(int cleared, int total, int ProgressiveLifebarDifficulty) { }; + virtual void ForceFail(); private: - float m_fLifeRemainingSeconds; - AutoActor m_sprFrame; - BitmapText m_textTimeRemaining; - MeterDisplay m_Meter; + float GetLifeSeconds() const; - apActorCommands m_TapCommands[NUM_TAP_NOTE_SCORES]; - apActorCommands m_GainLife; + AutoActor m_sprBackground; + Quad m_quadDangerGlow; + StreamDisplay* m_pStream; + AutoActor m_sprFrame; + + float m_fLifeTotalGainedSeconds; + float m_fLifeTotalLostSeconds; RageSound m_soundGainLife; }; diff --git a/stepmania/src/Makefile.am b/stepmania/src/Makefile.am index cfe9b77e72..fb20304e9d 100644 --- a/stepmania/src/Makefile.am +++ b/stepmania/src/Makefile.am @@ -210,7 +210,8 @@ endif ActorsInGameplayAndMenus = \ BGAnimation.cpp BGAnimation.h BGAnimationLayer.cpp BGAnimationLayer.h Banner.cpp Banner.h \ -ConditionalBGA.cpp ConditionalBGA.h DifficultyIcon.cpp DifficultyIcon.h MeterDisplay.cpp MeterDisplay.h \ +ConditionalBGA.cpp ConditionalBGA.h DifficultyIcon.cpp DifficultyIcon.h \ +MeterDisplay.cpp MeterDisplay.h StreamDisplay.cpp StreamDisplay.h Transition.cpp Transition.h ActorsInMenus = \ @@ -244,6 +245,7 @@ ReceptorArrow.cpp ReceptorArrow.h ReceptorArrowRow.cpp ReceptorArrowRow.h \ ScoreDisplay.cpp ScoreDisplay.h \ ScoreDisplayBattle.cpp ScoreDisplayBattle.h \ ScoreDisplayCalories.cpp ScoreDisplayCalories.h \ +ScoreDisplayLifeTime.cpp ScoreDisplayLifeTime.h ScoreDisplayNormal.cpp ScoreDisplayNormal.h ScoreDisplayOni.cpp ScoreDisplayOni.h \ ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h diff --git a/stepmania/src/Player.cpp b/stepmania/src/Player.cpp index 31287b4ea7..8639966930 100644 --- a/stepmania/src/Player.cpp +++ b/stepmania/src/Player.cpp @@ -945,12 +945,15 @@ void Player::HandleStep( int col, const RageTimer &tm, bool bHeld ) if( m_pLifeMeter ) m_pLifeMeter->ChangeLife( score ); - + if( m_pScoreDisplay ) + m_pScoreDisplay->OnJudgment( score ); + if( m_pSecondaryScoreDisplay ) + m_pSecondaryScoreDisplay->OnJudgment( score ); // TODO: Remove use of PlayerNumber PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - if( m_pCombinedLifeMeter ) m_pCombinedLifeMeter->ChangeLife( pn, score ); + tn.result.bHidden = true; m_NoteData.SetTapNote( col, iIndexOverlappingNote, tn ); if( m_pNoteField ) @@ -1473,18 +1476,26 @@ void Player::HandleTapRowScore( unsigned row ) if( m_pPlayerStageStats ) m_pPlayerStageStats->SetLifeRecordAt( life, STATSMAN->m_CurStageStats.fGameplaySeconds ); - if (m_pScoreDisplay) + if( m_pScoreDisplay ) + { if( m_pPlayerStageStats ) - m_pScoreDisplay->SetScore(m_pPlayerStageStats->iScore); - if (m_pSecondaryScoreDisplay) + m_pScoreDisplay->SetScore( m_pPlayerStageStats->iScore ); + m_pScoreDisplay->OnJudgment( scoreOfLastTap ); + } + if( m_pSecondaryScoreDisplay ) + { if( m_pPlayerStageStats ) - m_pSecondaryScoreDisplay->SetScore(m_pPlayerStageStats->iScore); + m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->iScore ); + m_pSecondaryScoreDisplay->OnJudgment( scoreOfLastTap ); + } - if( m_pLifeMeter ) { + if( m_pLifeMeter ) + { m_pLifeMeter->ChangeLife( scoreOfLastTap ); m_pLifeMeter->OnDancePointsChange(); // update oni life meter } - if( m_pCombinedLifeMeter ) { + if( m_pCombinedLifeMeter ) + { m_pCombinedLifeMeter->ChangeLife( pn, scoreOfLastTap ); m_pCombinedLifeMeter->OnDancePointsChange( pn ); // update oni life meter } @@ -1498,7 +1509,7 @@ void Player::HandleHoldScore( HoldNoteScore holdScore, TapNoteScore tapScore ) NoCheating = false; #endif - if(GAMESTATE->m_bDemonstrationOrJukebox) + if( GAMESTATE->m_bDemonstrationOrJukebox ) NoCheating = false; // don't accumulate points if AutoPlay is on. if( NoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) @@ -1512,12 +1523,18 @@ void Player::HandleHoldScore( HoldNoteScore holdScore, TapNoteScore tapScore ) // TODO: Remove use of PlayerNumber. PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - if (m_pScoreDisplay) - if( m_pPlayerStageStats ) - m_pScoreDisplay->SetScore(m_pPlayerStageStats->iScore); - if (m_pSecondaryScoreDisplay) - if( m_pPlayerStageStats ) - m_pSecondaryScoreDisplay->SetScore(m_pPlayerStageStats->iScore); + if( m_pScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pScoreDisplay->SetScore( m_pPlayerStageStats->iScore ); + m_pScoreDisplay->OnJudgment( holdScore, tapScore ); + } + if( m_pSecondaryScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->iScore ); + m_pSecondaryScoreDisplay->OnJudgment( holdScore, tapScore ); + } if( m_pLifeMeter ) { diff --git a/stepmania/src/PlayerStageStats.cpp b/stepmania/src/PlayerStageStats.cpp index 476544e4bc..6703d8101b 100644 --- a/stepmania/src/PlayerStageStats.cpp +++ b/stepmania/src/PlayerStageStats.cpp @@ -24,6 +24,8 @@ void PlayerStageStats::Init() iSongsPassed = iSongsPlayed = 0; iTotalError = 0; fCaloriesBurned = 0; + iTotalError = 0; + fLifeRemainingSeconds = 0; ZERO( iTapNoteScores ); ZERO( iHoldNoteScores ); @@ -63,6 +65,7 @@ void PlayerStageStats::AddStats( const PlayerStageStats& other ) iSongsPlayed += other.iSongsPlayed; iTotalError += other.iTotalError; fCaloriesBurned += other.fCaloriesBurned; + fLifeRemainingSeconds = other.fLifeRemainingSeconds; // don't accumulate const float fOtherFirstSecond = other.fFirstSecond + fLastSecond; const float fOtherLastSecond = other.fLastSecond + fLastSecond; diff --git a/stepmania/src/PlayerStageStats.h b/stepmania/src/PlayerStageStats.h index 331362c7b9..9b02a1afe4 100644 --- a/stepmania/src/PlayerStageStats.h +++ b/stepmania/src/PlayerStageStats.h @@ -52,6 +52,7 @@ struct PlayerStageStats int iSongsPassed; int iSongsPlayed; int iTotalError; + float fLifeRemainingSeconds; // used in survival // workout float fCaloriesBurned; diff --git a/stepmania/src/ScoreDisplay.h b/stepmania/src/ScoreDisplay.h index bb1bb5de98..6c4251eff9 100644 --- a/stepmania/src/ScoreDisplay.h +++ b/stepmania/src/ScoreDisplay.h @@ -3,6 +3,7 @@ #include "PlayerNumber.h" #include "ActorFrame.h" +#include "GameConstantsAndTypes.h" struct PlayerState; @@ -12,6 +13,14 @@ public: virtual void Init( const PlayerState* pPlayerState ) { m_pPlayerState = pPlayerState; } virtual void SetScore( int iNewScore ) {} + virtual void OnLoadSong() {}; + virtual void OnSongEnded() {}; + /* Notification of a tap note judgment. This *is* called for + * the head of hold notes. */ + virtual void OnJudgment( TapNoteScore score ) {}; + /* Notification of a hold judgment. tscore is the score + * received for the initial tap note. */ + virtual void OnJudgment( HoldNoteScore score, TapNoteScore tscore ) {}; protected: const PlayerState* m_pPlayerState; // needed to look up statistics in GAMESTATE diff --git a/stepmania/src/ScoreDisplayLifeTime.cpp b/stepmania/src/ScoreDisplayLifeTime.cpp new file mode 100644 index 0000000000..c334709f20 --- /dev/null +++ b/stepmania/src/ScoreDisplayLifeTime.cpp @@ -0,0 +1,154 @@ +#include "global.h" +#include "ScoreDisplayLifeTime.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "PrefsManager.h" +#include "GameState.h" +#include "ThemeManager.h" +#include "PlayerState.h" +#include "StatsManager.h" +#include "CommonMetrics.h" +#include "ActorUtil.h" + +static const CString GAIN_LIFE_COMMAND_NAME = "GainLife"; + +ScoreDisplayLifeTime::ScoreDisplayLifeTime() +{ + LOG->Trace( "ScoreDisplayLifeTime::ScoreDisplayLifeTime()" ); +} + +void ScoreDisplayLifeTime::Init( const PlayerState* pPlayerState ) +{ + ScoreDisplay::Init( pPlayerState ); + + const CString sType = "ScoreDisplayLifeTime"; + + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = pPlayerState->m_PlayerNumber; + + m_sprFrame.Load( THEME->GetPathG(sType,"frame") ); + m_sprFrame->SetName( "Frame" ); + this->AddChild( m_sprFrame ); + ActorUtil::OnCommand( m_sprFrame, sType ); + + m_textTimeRemaining.LoadFromFont( THEME->GetPathF(sType, "TimeRemaining") ); + m_textTimeRemaining.SetDiffuse( PLAYER_COLOR.GetValue(pn) ); + m_textTimeRemaining.SetName( "TimeRemaining" ); + this->AddChild( &m_textTimeRemaining ); + ActorUtil::OnCommand( m_textTimeRemaining, sType ); + + m_textDeltaSeconds.LoadFromFont( THEME->GetPathF(sType,"DeltaSeconds") ); + m_textDeltaSeconds.SetName( "DeltaSeconds" ); + this->AddChild( &m_textDeltaSeconds ); + ActorUtil::OnCommand( m_textDeltaSeconds, sType ); + + FOREACH_TapNoteScore( tns ) + { + const CString &sCommand = TapNoteScoreToString(tns); + if( !m_textDeltaSeconds.HasCommand( sCommand ) ) + ActorUtil::LoadCommand( m_textDeltaSeconds, sType, sCommand ); + } + FOREACH_HoldNoteScore( hns ) + { + const CString &sCommand = HoldNoteScoreToString(hns); + if( !m_textDeltaSeconds.HasCommand( sCommand ) ) + ActorUtil::LoadCommand( m_textDeltaSeconds, sType, sCommand ); + } + { + if( !m_textDeltaSeconds.HasCommand( GAIN_LIFE_COMMAND_NAME ) ) + ActorUtil::LoadCommand( m_textDeltaSeconds, sType, GAIN_LIFE_COMMAND_NAME ); + } +} + +void ScoreDisplayLifeTime::Update( float fDelta ) +{ + ScoreDisplay::Update( fDelta ); + + float fSecs = STATSMAN->m_CurStageStats.m_player[m_pPlayerState->m_PlayerNumber].fLifeRemainingSeconds; + + CString s = SecondsToMMSSMsMs(fSecs); + s.erase( s.begin() ); // erase first digit + m_textTimeRemaining.SetText( SecondsToMMSSMsMs(fSecs) ); +} + +void ScoreDisplayLifeTime::OnLoadSong() +{ + m_textTimeRemaining.PlayCommand( GAIN_LIFE_COMMAND_NAME ); +} + +void ScoreDisplayLifeTime::OnSongEnded() +{ +} + +void ScoreDisplayLifeTime::OnJudgment( TapNoteScore tns ) +{ + if( STATSMAN->m_CurStageStats.m_player[m_pPlayerState->m_PlayerNumber].bFailedEarlier ) + return; + + float fMeterChange = 0; + switch( tns ) + { + case TNS_MARVELOUS: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeMarvelous; break; + case TNS_PERFECT: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangePerfect; break; + case TNS_GREAT: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeGreat; break; + case TNS_GOOD: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeGood; break; + case TNS_BOO: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeBoo; break; + case TNS_MISS: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeMiss; break; + case TNS_HIT_MINE: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeHitMine; break; + default: ASSERT(0); + } + + if( fMeterChange != 0 ) + { + CString s = ssprintf( fMeterChange>0 ? "%.0fms" : "-%.0fms",fMeterChange*1000); + m_textDeltaSeconds.SetText( s ); + m_textDeltaSeconds.PlayCommand( TapNoteScoreToString(tns) ); + } +} + +void ScoreDisplayLifeTime::OnJudgment( HoldNoteScore hns, TapNoteScore tns ) +{ + if( STATSMAN->m_CurStageStats.m_player[m_pPlayerState->m_PlayerNumber].bFailedEarlier ) + return; + + float fMeterChange = 0; + switch( hns ) + { + case HNS_OK: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeOK; break; + case HNS_NG: fMeterChange = PREFSMAN->m_fTimeMeterSecondsChangeNG; break; + default: ASSERT(0); + } + + if( fMeterChange != 0 ) + { + CString s = ssprintf( fMeterChange>0 ? "%.0fms" : "-%.0fms",fMeterChange*1000); + m_textDeltaSeconds.SetText( s ); + m_textDeltaSeconds.PlayCommand( HoldNoteScoreToString(hns) ); + } +} + + +/* + * (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. + */ diff --git a/stepmania/src/ScoreDisplayLifeTime.h b/stepmania/src/ScoreDisplayLifeTime.h new file mode 100644 index 0000000000..fc11c04f7e --- /dev/null +++ b/stepmania/src/ScoreDisplayLifeTime.h @@ -0,0 +1,55 @@ +/* ScoreDisplayLifeTime - Display a updating count of life time remaining. */ + +#ifndef ScoreDisplayLifeTime_H +#define ScoreDisplayLifeTime_H + +#include "ScoreDisplay.h" +#include "BitmapText.h" +#include "AutoActor.h" + +class ScoreDisplayLifeTime : public ScoreDisplay +{ +public: + ScoreDisplayLifeTime(); + + virtual void Init( const PlayerState* pPlayerState ); + + virtual void Update( float fDelta ); + + virtual void OnLoadSong(); + virtual void OnSongEnded(); + virtual void OnJudgment( TapNoteScore score ); + virtual void OnJudgment( HoldNoteScore score, TapNoteScore tscore ); + +protected: + AutoActor m_sprFrame; + BitmapText m_textTimeRemaining; + BitmapText m_textDeltaSeconds; +}; + +#endif + +/* + * (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. + */ diff --git a/stepmania/src/ScoreDisplayPercentage.cpp b/stepmania/src/ScoreDisplayPercentage.cpp index ed8969fbae..dccbce9422 100644 --- a/stepmania/src/ScoreDisplayPercentage.cpp +++ b/stepmania/src/ScoreDisplayPercentage.cpp @@ -8,7 +8,7 @@ ScoreDisplayPercentage::ScoreDisplayPercentage() { m_sprFrame.Load( THEME->GetPathG("ScoreDisplayNormal","frame") ); - this->AddChild( &m_sprFrame ); + this->AddChild( m_sprFrame ); m_Percent.SetName( "ScoreDisplayPercentage Percent" ); this->AddChild( &m_Percent ); diff --git a/stepmania/src/ScoreDisplayPercentage.h b/stepmania/src/ScoreDisplayPercentage.h index 128bdba7ad..955e81a5aa 100644 --- a/stepmania/src/ScoreDisplayPercentage.h +++ b/stepmania/src/ScoreDisplayPercentage.h @@ -5,7 +5,7 @@ #include "ScoreDisplay.h" #include "PercentageDisplay.h" -#include "Sprite.h" +#include "AutoActor.h" class ScoreDisplayPercentage: public ScoreDisplay { @@ -14,8 +14,8 @@ public: void Init( const PlayerState* pPlayerState ); private: + AutoActor m_sprFrame; PercentageDisplay m_Percent; - Sprite m_sprFrame; }; #endif diff --git a/stepmania/src/ScreenGameplay.cpp b/stepmania/src/ScreenGameplay.cpp index b375bc91b2..a46f21117d 100644 --- a/stepmania/src/ScreenGameplay.cpp +++ b/stepmania/src/ScreenGameplay.cpp @@ -13,6 +13,7 @@ #include "GameState.h" #include "ScoreDisplayNormal.h" #include "ScoreDisplayPercentage.h" +#include "ScoreDisplayLifeTime.h" #include "ScoreDisplayOni.h" #include "ScoreDisplayBattle.h" #include "ScoreDisplayRave.h" @@ -414,7 +415,10 @@ void ScreenGameplay::Init() break; case PLAY_MODE_ONI: case PLAY_MODE_ENDLESS: - m_pPrimaryScoreDisplay[p] = new ScoreDisplayOni; + if( GAMESTATE->m_SongOptions.m_LifeType == SongOptions::LIFE_TIME ) + m_pPrimaryScoreDisplay[p] = new ScoreDisplayLifeTime; + else + m_pPrimaryScoreDisplay[p] = new ScoreDisplayOni; break; default: ASSERT(0); @@ -1045,10 +1049,14 @@ void ScreenGameplay::LoadNextSong() // give a little life back between stages if( m_pLifeMeter[p] ) m_pLifeMeter[p]->OnLoadSong(); - if( m_pCombinedLifeMeter ) - m_pCombinedLifeMeter->OnLoadSong(); + if( m_pPrimaryScoreDisplay[p] ) + m_pPrimaryScoreDisplay[p]->OnLoadSong(); + if( m_pSecondaryScoreDisplay[p] ) + m_pSecondaryScoreDisplay[p]->OnLoadSong(); } } + if( m_pCombinedLifeMeter ) + m_pCombinedLifeMeter->OnLoadSong(); m_SongForeground.LoadFromSong( GAMESTATE->m_pCurSong ); @@ -1552,8 +1560,12 @@ void ScreenGameplay::Update( float fDeltaTime ) case SongOptions::FAIL_IMMEDIATE: case SongOptions::FAIL_COMBO_OF_30_MISSES: case SongOptions::FAIL_END_OF_SONG: - FOREACH_EnabledPlayer(pn) - STATSMAN->m_CurStageStats.m_player[pn].bFailed = true; // fail + FOREACH_EnabledPlayer(p) + { + STATSMAN->m_CurStageStats.m_player[p].bFailed = true; // fail + m_pLifeMeter[p]->ForceFail(); + STATSMAN->m_CurStageStats.m_player[p].SetLifeRecordAt( 0, STATSMAN->m_CurStageStats.fGameplaySeconds ); + } } this->PostScreenMessage( SM_NotesEnded, 0 ); @@ -2280,17 +2292,22 @@ void ScreenGameplay::HandleScreenMessage( const ScreenMessage SM ) m_pSoundMusic->Stop(); /* Next song. */ + + // give a little life back between stages FOREACH_EnabledPlayer(p) { if( !STATSMAN->m_CurStageStats.m_player[p].bFailed ) { - // give a little life back between stages if( m_pLifeMeter[p] ) m_pLifeMeter[p]->OnSongEnded(); - if( m_pCombinedLifeMeter ) - m_pCombinedLifeMeter->OnSongEnded(); + if( m_pPrimaryScoreDisplay[p] ) + m_pPrimaryScoreDisplay[p]->OnSongEnded(); + if( m_pSecondaryScoreDisplay[p] ) + m_pSecondaryScoreDisplay[p]->OnSongEnded(); } } + if( m_pCombinedLifeMeter ) + m_pCombinedLifeMeter->OnSongEnded(); int iPlaySongIndex = GAMESTATE->GetCourseSongIndex()+1; iPlaySongIndex %= m_apSongsQueue.size(); diff --git a/stepmania/src/StepMania.dsp b/stepmania/src/StepMania.dsp index d21403cce1..386e507a5f 100644 --- a/stepmania/src/StepMania.dsp +++ b/stepmania/src/StepMania.dsp @@ -62,7 +62,7 @@ IntDir=.\../Debug6 TargetDir=\stepmania\stepmania\Program TargetName=StepMania-debug SOURCE="$(InputPath)" -PreLink_Cmds=archutils\Win32\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=archutils\Win32\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=archutils\Win32\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi # End Special Build Tool @@ -99,7 +99,7 @@ IntDir=.\../Release6 TargetDir=\stepmania\stepmania\Program TargetName=StepMania SOURCE="$(InputPath)" -PreLink_Cmds=archutils\Win32\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=archutils\Win32\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=archutils\Win32\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi # End Special Build Tool @@ -1866,6 +1866,14 @@ SOURCE=.\MeterDisplay.h # End Source File # Begin Source File +SOURCE=.\StreamDisplay.cpp +# End Source File +# Begin Source File + +SOURCE=.\StreamDisplay.h +# End Source File +# Begin Source File + SOURCE=.\Transition.cpp # End Source File # Begin Source File @@ -2114,6 +2122,14 @@ SOURCE=.\ScoreDisplayCalories.h # End Source File # Begin Source File +SOURCE=.\ScoreDisplayLifeTime.cpp +# End Source File +# Begin Source File + +SOURCE=.\ScoreDisplayLifeTime.h +# End Source File +# Begin Source File + SOURCE=.\ScoreDisplayNormal.cpp # End Source File # Begin Source File diff --git a/stepmania/src/StepMania.vcproj b/stepmania/src/StepMania.vcproj index 3cb9c99e87..711e64ba4a 100644 --- a/stepmania/src/StepMania.vcproj +++ b/stepmania/src/StepMania.vcproj @@ -1466,6 +1466,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + + @@ -1882,6 +1888,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + + diff --git a/stepmania/src/StreamDisplay.cpp b/stepmania/src/StreamDisplay.cpp new file mode 100644 index 0000000000..b64130a4ad --- /dev/null +++ b/stepmania/src/StreamDisplay.cpp @@ -0,0 +1,258 @@ +#include "global.h" +#include "StreamDisplay.h" +#include "GameState.h" + + +void StreamDisplay::Load( + float fMeterWidth, + float fMeterHeight, + int iNumStrips, + int iNumChambers, + const CString &sNormalPath, + const CString &sHotPath, + const CString &sPassingPath + ) +{ + m_fMeterWidth = fMeterWidth; + m_fMeterHeight = fMeterHeight; + m_iNumStrips = iNumStrips; + m_iNumChambers = iNumChambers; + + m_quadMask.SetDiffuse( RageColor(0,0,0,1) ); + m_quadMask.SetZ( 1 ); + m_quadMask.SetBlendMode( BLEND_NO_EFFECT ); + m_quadMask.SetUseZBuffer( true ); + + CString sGraphicPath; + RageTextureID ID; + ID.bStretch = true; + + ID.filename = sNormalPath; + m_sprStreamNormal.Load( ID ); + m_sprStreamNormal.SetUseZBuffer( true ); + + ID.filename = sHotPath; + m_sprStreamHot.Load( ID ); + m_sprStreamHot.SetUseZBuffer( true ); + + ID.filename = sPassingPath; + m_sprStreamPassing.Load( ID ); + m_sprStreamPassing.SetUseZBuffer( true ); +} + +void StreamDisplay::Update( float fDeltaSecs ) +{ + // HACK: Tweaking these values is very difficulty. Update the + // "physics" many times so that the spring motion appears faster + for( int i=0; i<10; i++ ) + { + const float fDelta = m_fPercent - m_fTrailingPercent; + + // Don't apply spring and viscous forces if we're full or empty. + // Just move straight to either full or empty. + if( m_fPercent <= 0 || m_fPercent >= 1 ) + { + m_fVelocity = (fDelta / fabsf(fDelta)) * 4; + } + else + { + const float fSpringForce = fDelta * 2.0f; + m_fVelocity += fSpringForce * fDeltaSecs; + + const float fViscousForce = -m_fVelocity * 0.2f; + m_fVelocity += fViscousForce * fDeltaSecs; + } + + CLAMP( m_fVelocity, -.06f, +.02f ); + + m_fTrailingPercent += m_fVelocity * fDeltaSecs; + } + + // Don't clamp life percentage a little outside the visible range so + // that the clamp doesn't dampen the "jiggle" of the meter. + CLAMP( m_fTrailingPercent, -0.1f, 1.1f ); +} + +void StreamDisplay::DrawPrimitives() +{ + DrawMask( m_fTrailingPercent ); // this is the "right endcap" to the life + + const float fChamberWidthInPercent = 1.0f/m_iNumChambers; + float fPercentBetweenStrips = 1.0f/m_iNumStrips; + // round this so that the chamber overflows align + if( m_iNumChambers > 10 ) + fPercentBetweenStrips = Quantize( fPercentBetweenStrips, fChamberWidthInPercent ); + + float fPercentOffset = fmodf( GAMESTATE->m_fSongBeat/4+1000, fPercentBetweenStrips ); + ASSERT( fPercentOffset >= 0 && fPercentOffset <= fPercentBetweenStrips ); + + for( float f=fPercentOffset+1; f>=0; f-=fPercentBetweenStrips ) + { + DrawMask( f ); + DrawStrip( f ); + } +} + +void StreamDisplay::SetPercent( float fPercent ) +{ + float fDeltaPercent = fPercent - m_fPercent; + m_fVelocity += fDeltaPercent; + m_fPercent = fPercent; +} + +void StreamDisplay::SetPassingAlpha( float fPassingAlpha ) +{ + m_fPassingAlpha = fPassingAlpha; +} + +void StreamDisplay::SetHotAlpha( float fHotAlpha ) +{ + m_fHotAlpha = fHotAlpha; +} + +void StreamDisplay::GetChamberIndexAndOverslow( float fPercent, int& iChamberOut, float& fChamberOverflowPercentOut ) +{ + iChamberOut = (int)(fPercent*m_iNumChambers); + fChamberOverflowPercentOut = fPercent*m_iNumChambers - iChamberOut; +} + +float StreamDisplay::GetChamberLeftPercent( int iChamber ) +{ + return (iChamber+0) / (float)m_iNumChambers; +} + +float StreamDisplay::GetChamberRightPercent( int iChamber ) +{ + return (iChamber+1) / (float)m_iNumChambers; +} + +float StreamDisplay::GetRightEdgePercent( int iChamber, float fChamberOverflowPercent ) +{ + if( (iChamber%2) == 0 ) + return (iChamber+fChamberOverflowPercent) / (float)m_iNumChambers; + else + return (iChamber+1) / (float)m_iNumChambers; +} + +float StreamDisplay::GetHeightPercent( int iChamber, float fChamberOverflowPercent ) +{ + if( (iChamber%2) == 1 ) + return 1-fChamberOverflowPercent; + else + return 0; +} + +void StreamDisplay::DrawStrip( float fRightEdgePercent ) +{ + RectF rect; + + const float fChamberWidthInPercent = 1.0f/m_iNumChambers; + const float fStripWidthInPercent = 1.0f/m_iNumStrips; + + const float fCorrectedRightEdgePercent = fRightEdgePercent + fChamberWidthInPercent; + const float fCorrectedStripWidthInPercent = fStripWidthInPercent + 2*fChamberWidthInPercent; + const float fCorrectedLeftEdgePercent = fCorrectedRightEdgePercent - fCorrectedStripWidthInPercent; + + + // set size of streams + rect.left = -m_fMeterWidth/2 + m_fMeterWidth*max(0,fCorrectedLeftEdgePercent); + rect.top = -m_fMeterHeight/2; + rect.right = -m_fMeterWidth/2 + m_fMeterWidth*min(1,fCorrectedRightEdgePercent); + rect.bottom = +m_fMeterHeight/2; + + ASSERT( rect.left <= m_fMeterWidth/2 && rect.right <= m_fMeterWidth/2 ); + + float fPercentCroppedFromLeft = max( 0, -fCorrectedLeftEdgePercent ); + float fPercentCroppedFromRight = max( 0, fCorrectedRightEdgePercent-1 ); + + + m_sprStreamNormal.StretchTo( rect ); + m_sprStreamPassing.StretchTo( rect ); + m_sprStreamHot.StretchTo( rect ); + + + // set custom texture coords +// float fPrecentOffset = fRightEdgePercent; + + RectF frectCustomTexRect( + fPercentCroppedFromLeft, + 0, + 1-fPercentCroppedFromRight, + 1); + + m_sprStreamNormal.SetCustomTextureRect( frectCustomTexRect ); + m_sprStreamPassing.SetCustomTextureRect( frectCustomTexRect ); + m_sprStreamHot.SetCustomTextureRect( frectCustomTexRect ); + + m_sprStreamPassing.SetDiffuse( RageColor(1,1,1,m_fPassingAlpha) ); + m_sprStreamHot.SetDiffuse( RageColor(1,1,1,m_fHotAlpha) ); + + if( m_fPassingAlpha < 1 && m_fHotAlpha < 1) + m_sprStreamNormal.Draw(); + if( m_fHotAlpha < 1) + m_sprStreamPassing.Draw(); + m_sprStreamHot.Draw(); +} + +void StreamDisplay::DrawMask( float fPercent ) +{ + RectF rect; + + int iChamber; + float fChamberOverflowPercent; + GetChamberIndexAndOverslow( fPercent, iChamber, fChamberOverflowPercent ); + float fRightPercent = GetRightEdgePercent( iChamber, fChamberOverflowPercent ); + float fHeightPercent = GetHeightPercent( iChamber, fChamberOverflowPercent ); + float fChamberLeftPercent = GetChamberLeftPercent( iChamber ); + float fChamberRightPercent = GetChamberRightPercent( iChamber ); + + // draw mask for vertical chambers + rect.left = -m_fMeterWidth/2 + fChamberLeftPercent*m_fMeterWidth-1; + rect.top = -m_fMeterHeight/2; + rect.right = -m_fMeterWidth/2 + fChamberRightPercent*m_fMeterWidth+1; + rect.bottom = -m_fMeterHeight/2 + fHeightPercent*m_fMeterHeight; + + rect.left = MIN( rect.left, + m_fMeterWidth/2 ); + rect.right = MIN( rect.right, + m_fMeterWidth/2 ); + + m_quadMask.StretchTo( rect ); + m_quadMask.Draw(); + + // draw mask for horizontal chambers + rect.left = -m_fMeterWidth/2 + fRightPercent*m_fMeterWidth; + rect.top = -m_fMeterHeight/2; + rect.right = +m_fMeterWidth/2; + rect.bottom = +m_fMeterHeight/2; + + rect.left = MIN( rect.left, + m_fMeterWidth/2 ); + rect.right = MIN( rect.right, + m_fMeterWidth/2 ); + + m_quadMask.StretchTo( rect ); + m_quadMask.Draw(); +} + + +/* + * (c) 2003-2004 Chris Danford + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/StreamDisplay.h b/stepmania/src/StreamDisplay.h new file mode 100644 index 0000000000..8184a940a5 --- /dev/null +++ b/stepmania/src/StreamDisplay.h @@ -0,0 +1,90 @@ +#ifndef StreamDisplay_H +#define StreamDisplay_H + +#include "Sprite.h" +#include "Quad.h" + +class StreamDisplay : public Actor +{ +public: + StreamDisplay() + { + m_fMeterWidth = 0; + m_fMeterHeight = 0; + m_iNumStrips = 1; + m_iNumChambers = 1; + m_fPercent = 0; + m_fPassingAlpha = 0; + m_fHotAlpha = 0; + } + + virtual void Update( float fDeltaSecs ); + + void Load( + float fMeterWidth, + float fMeterHeight, + int iNumStrips, + int iNumChambers, + const CString &sNormalPath, + const CString &sHotPath, + const CString &sPassingPath + ); + + void DrawPrimitives(); + void SetPercent( float fPercent ); + void SetPassingAlpha( float fPassingAlpha ); + void SetHotAlpha( float fHotAlpha ); + + float GetTrailingLifePercent() { return m_fTrailingPercent; } + +private: + Sprite m_sprStreamNormal; + Sprite m_sprStreamHot; + Sprite m_sprStreamPassing; + Quad m_quadMask; + + float m_fMeterHeight; + float m_fMeterWidth; + int m_iNumStrips; + int m_iNumChambers; + float m_fPercent; + float m_fTrailingPercent; // this approaches m_fPercent + float m_fVelocity; // of m_fTrailingPercent + float m_fPassingAlpha; + float m_fHotAlpha; + + void GetChamberIndexAndOverslow( float fPercent, int& iChamberOut, float& fChamberOverflowPercentOut ); + float GetChamberLeftPercent( int iChamber ); + float GetChamberRightPercent( int iChamber ); + float GetRightEdgePercent( int iChamber, float fChamberOverflowPercent ); + float GetHeightPercent( int iChamber, float fChamberOverflowPercent ); + void DrawStrip( float fRightEdgePercent ); + void DrawMask( float fPercent ); +}; + +#endif + +/* + * (c) 2003-2004 Chris Danford + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */