2011-03-17 01:47:30 -04:00
#include "global.h"
#include "NoteField.h"
#include "RageUtil.h"
#include "GameConstantsAndTypes.h"
#include "ArrowEffects.h"
#include "GameManager.h"
#include "GameState.h"
#include "RageTimer.h"
#include "RageLog.h"
#include "RageMath.h"
#include "ThemeManager.h"
#include "NoteSkinManager.h"
#include "Song.h"
#include "ScreenDimensions.h"
#include "PlayerState.h"
#include "Style.h"
#include "CommonMetrics.h"
#include "BackgroundUtil.h"
#include "Course.h"
#include "NoteData.h"
2012-03-20 00:19:28 -05:00
#include "RageDisplay.h"
2011-03-17 01:47:30 -04:00
2023-04-19 14:22:59 +02:00
#include <cfloat>
#include <cmath>
2023-04-19 23:04:25 +02:00
#include <cstddef>
2023-04-20 19:02:13 +02:00
#include <vector>
2023-04-19 14:22:59 +02:00
2011-06-12 19:20:43 -04:00
float FindFirstDisplayedBeat ( const PlayerState * pPlayerState , int iDrawDistanceAfterTargetsPixels );
float FindLastDisplayedBeat ( const PlayerState * pPlayerState , int iDrawDistanceBeforeTargetsPixels );
2011-03-17 01:47:30 -04:00
static ThemeMetric < bool > SHOW_BOARD ( "NoteField" , "ShowBoard" );
static ThemeMetric < bool > SHOW_BEAT_BARS ( "NoteField" , "ShowBeatBars" );
static ThemeMetric < float > FADE_BEFORE_TARGETS_PERCENT ( "NoteField" , "FadeBeforeTargetsPercent" );
static ThemeMetric < float > BAR_MEASURE_ALPHA ( "NoteField" , "BarMeasureAlpha" );
static ThemeMetric < float > BAR_4TH_ALPHA ( "NoteField" , "Bar4thAlpha" );
static ThemeMetric < float > BAR_8TH_ALPHA ( "NoteField" , "Bar8thAlpha" );
static ThemeMetric < float > BAR_16TH_ALPHA ( "NoteField" , "Bar16thAlpha" );
static ThemeMetric < float > FADE_FAIL_TIME ( "NoteField" , "FadeFailTime" );
2023-04-19 23:04:25 +02:00
static RString RoutineNoteSkinName ( std :: size_t i ) { return ssprintf ( "RoutineNoteSkinP%i" , int ( i + 1 )); }
2011-03-17 01:47:30 -04:00
static ThemeMetric1D < RString > ROUTINE_NOTESKIN ( "NoteField" , RoutineNoteSkinName , NUM_PLAYERS );
NoteField :: NoteField ()
{
2019-06-22 12:35:38 -07:00
m_pNoteData = nullptr ;
m_pCurDisplay = nullptr ;
2015-04-03 14:19:28 -06:00
m_drawing_board_primitive = false ;
2024-02-15 14:37:56 -06:00
m_bShowBeatBars = SHOW_BEAT_BARS ;
2024-03-03 00:08:55 -08:00
m_fBarMeasureAlpha = BAR_MEASURE_ALPHA ;
m_fBar4thAlpha = BAR_4TH_ALPHA ;
m_fBar8thAlpha = BAR_8TH_ALPHA ;
m_fBar16thAlpha = BAR_16TH_ALPHA ;
2011-03-17 01:47:30 -04:00
m_textMeasureNumber . LoadFromFont ( THEME -> GetPathF ( "NoteField" , "MeasureNumber" ) );
m_textMeasureNumber . SetZoom ( 1.0f );
m_textMeasureNumber . SetShadowLength ( 2 );
m_textMeasureNumber . SetWrapWidthPixels ( 300 );
m_rectMarkerBar . SetEffectDiffuseShift ( 2 , RageColor ( 1 , 1 , 1 , 0.5f ), RageColor ( 0.5f , 0.5f , 0.5f , 0.5f ) );
m_sprBoard . Load ( THEME -> GetPathG ( "NoteField" , "board" ) );
m_sprBoard -> SetName ( "Board" );
m_sprBoard -> PlayCommand ( "On" );
this -> AddChild ( m_sprBoard );
m_fBoardOffsetPixels = 0 ;
m_fCurrentBeatLastUpdate = - 1 ;
m_fYPosCurrentBeatLastUpdate = - 1 ;
this -> SubscribeToMessage ( Message_CurrentSongChanged );
m_sprBeatBars . Load ( THEME -> GetPathG ( "NoteField" , "bars" ) );
m_sprBeatBars . StopAnimating ();
2014-12-26 01:54:34 -07:00
// I decided to do it this way because I don't want to dig through
// ScreenEdit to change all the places it touches the markers. -Kyz
2015-01-01 04:36:20 -07:00
m_FieldRenderArgs . selection_begin_marker = & m_iBeginMarker ;
m_FieldRenderArgs . selection_end_marker = & m_iEndMarker ;
2011-03-17 01:47:30 -04:00
m_iBeginMarker = m_iEndMarker = - 1 ;
2015-01-01 04:36:20 -07:00
m_FieldRenderArgs . fail_fade = - 1 ;
2014-11-30 23:17:12 -07:00
m_StepCallback . SetFromNil ();
m_SetPressedCallback . SetFromNil ();
m_DidTapNoteCallback . SetFromNil ();
m_DidHoldNoteCallback . SetFromNil ();
2011-03-17 01:47:30 -04:00
}
NoteField ::~ NoteField ()
{
Unload ();
}
void NoteField :: Unload ()
{
2022-07-10 18:28:56 +03:00
for ( std :: map < RString , NoteDisplayCols *>:: iterator it = m_NoteDisplays . begin ();
2011-03-17 01:47:30 -04:00
it != m_NoteDisplays . end (); ++ it )
delete it -> second ;
m_NoteDisplays . clear ();
2019-06-22 12:35:38 -07:00
m_pCurDisplay = nullptr ;
2011-03-17 01:47:30 -04:00
memset ( m_pDisplays , 0 , sizeof ( m_pDisplays ) );
}
2024-02-15 14:37:56 -06:00
void NoteField :: SetBeatBars ( bool active )
{
m_bShowBeatBars = active ;
}
bool NoteField :: GetBeatBars ()
{
return m_bShowBeatBars ;
}
2024-03-03 00:08:55 -08:00
void NoteField :: SetBeatBarsAlpha ( float measure , float fourth , float eighth , float sixteenth )
{
m_fBarMeasureAlpha = measure ;
m_fBar4thAlpha = fourth ;
m_fBar8thAlpha = eighth ;
m_fBar16thAlpha = sixteenth ;
}
2011-03-17 01:47:30 -04:00
void NoteField :: CacheNoteSkin ( const RString & sNoteSkin_ )
{
RString sNoteSkinLower = sNoteSkin_ ;
sNoteSkinLower . MakeLower ();
if ( m_NoteDisplays . find ( sNoteSkinLower ) != m_NoteDisplays . end () )
return ;
LockNoteSkin l ( sNoteSkinLower );
LOG -> Trace ( "NoteField::CacheNoteSkin: cache %s" , sNoteSkinLower . c_str () );
2014-12-07 01:53:17 -07:00
NoteDisplayCols * nd = new NoteDisplayCols ( GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer );
2011-03-17 01:47:30 -04:00
2014-12-07 01:53:17 -07:00
for ( int c = 0 ; c < GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer ; c ++ )
2011-03-17 01:47:30 -04:00
nd -> display [ c ]. Load ( c , m_pPlayerState , m_fYReverseOffsetPixels );
nd -> m_ReceptorArrowRow . Load ( m_pPlayerState , m_fYReverseOffsetPixels );
nd -> m_GhostArrowRow . Load ( m_pPlayerState , m_fYReverseOffsetPixels );
m_NoteDisplays [ sNoteSkinLower ] = nd ;
}
void NoteField :: UncacheNoteSkin ( const RString & sNoteSkin_ )
{
RString sNoteSkinLower = sNoteSkin_ ;
sNoteSkinLower . MakeLower ();
LOG -> Trace ( "NoteField::CacheNoteSkin: release %s" , sNoteSkinLower . c_str () );
ASSERT_M ( m_NoteDisplays . find ( sNoteSkinLower ) != m_NoteDisplays . end (), sNoteSkinLower );
delete m_NoteDisplays [ sNoteSkinLower ];
m_NoteDisplays . erase ( sNoteSkinLower );
}
void NoteField :: CacheAllUsedNoteSkins ()
{
// If we're in Routine mode, apply our per-player noteskins.
2014-12-07 01:53:17 -07:00
if ( GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_StyleType == StyleType_TwoPlayersSharedSides )
2011-03-17 01:47:30 -04:00
{
FOREACH_EnabledPlayer ( pn )
GAMESTATE -> ApplyStageModifiers ( pn , ROUTINE_NOTESKIN . GetValue ( pn ) );
}
/* Cache all note skins that we might need for the whole song, course or battle
* play, so we don't have to load them later (such as between course songs). */
2022-07-10 18:28:56 +03:00
std :: vector < RString > asSkinsLower ;
2011-03-17 01:47:30 -04:00
GAMESTATE -> GetAllUsedNoteSkins ( asSkinsLower );
asSkinsLower . push_back ( m_pPlayerState -> m_PlayerOptions . GetStage (). m_sNoteSkin );
2019-06-22 12:35:38 -07:00
for ( RString & s : asSkinsLower )
2015-01-31 00:23:54 -07:00
{
2019-06-22 12:35:38 -07:00
NOTESKIN -> ValidateNoteSkinName ( s );
s . MakeLower ();
2015-01-31 00:23:54 -07:00
}
2011-03-17 01:47:30 -04:00
for ( unsigned i = 0 ; i < asSkinsLower . size (); ++ i )
CacheNoteSkin ( asSkinsLower [ i ] );
/* If we're changing note skins in the editor, we can have old note skins lying
* around. Remove them so they don't accumulate. */
2022-07-10 18:28:56 +03:00
std :: set < RString > setNoteSkinsToUnload ;
2019-06-22 12:35:38 -07:00
for ( std :: pair < RString const & , NoteDisplayCols *> d : m_NoteDisplays )
2011-03-17 01:47:30 -04:00
{
2019-06-22 12:35:38 -07:00
bool unused = find ( asSkinsLower . begin (), asSkinsLower . end (), d . first ) == asSkinsLower . end ();
2011-03-17 01:47:30 -04:00
if ( unused )
2019-06-22 12:35:38 -07:00
setNoteSkinsToUnload . insert ( d . first );
2011-03-17 01:47:30 -04:00
}
2019-06-22 12:35:38 -07:00
for ( RString const & skin : setNoteSkinsToUnload )
UncacheNoteSkin ( skin );
2011-03-17 01:47:30 -04:00
RString sCurrentNoteSkinLower = m_pPlayerState -> m_PlayerOptions . GetCurrent (). m_sNoteSkin ;
2015-01-31 00:23:54 -07:00
NOTESKIN -> ValidateNoteSkinName ( sCurrentNoteSkinLower );
2011-03-17 01:47:30 -04:00
sCurrentNoteSkinLower . MakeLower ();
2022-07-10 18:28:56 +03:00
std :: map < RString , NoteDisplayCols *>:: iterator it = m_NoteDisplays . find ( sCurrentNoteSkinLower );
2011-03-17 01:47:30 -04:00
ASSERT_M ( it != m_NoteDisplays . end (), sCurrentNoteSkinLower );
m_pCurDisplay = it -> second ;
memset ( m_pDisplays , 0 , sizeof ( m_pDisplays ) );
FOREACH_EnabledPlayer ( pn )
{
RString sNoteSkinLower = GAMESTATE -> m_pPlayerState [ pn ] -> m_PlayerOptions . GetCurrent (). m_sNoteSkin ;
2015-01-31 00:23:54 -07:00
NOTESKIN -> ValidateNoteSkinName ( sNoteSkinLower );
2011-03-17 01:47:30 -04:00
sNoteSkinLower . MakeLower ();
it = m_NoteDisplays . find ( sNoteSkinLower );
ASSERT_M ( it != m_NoteDisplays . end (), sNoteSkinLower );
m_pDisplays [ pn ] = it -> second ;
}
2014-12-26 01:54:34 -07:00
InitColumnRenderers ();
2011-03-17 01:47:30 -04:00
}
2014-12-07 01:53:17 -07:00
void NoteField :: Init ( const PlayerState * pPlayerState , float fYReverseOffsetPixels , bool use_states_zoom )
2011-03-17 01:47:30 -04:00
{
m_pPlayerState = pPlayerState ;
m_fYReverseOffsetPixels = fYReverseOffsetPixels ;
CacheAllUsedNoteSkins ();
2014-12-07 01:53:17 -07:00
// Design change: Instead of having a flag in the style that toggles a
// fixed zoom that is only applied to the columns, ScreenGameplay now
// calculates a zoom factor to apply to the notefield and puts it in the
// PlayerState. -Kyz
// use_states_zoom flag exists because edit mode has to set its own special
// zoom factor. -Kyz
if ( use_states_zoom )
{
SetZoom ( pPlayerState -> m_NotefieldZoom );
}
2015-04-03 14:19:28 -06:00
// Pass the player state info down to children so that they can set
// per-player things. For example, if a screen filter is in the notefield
// board, this tells it what player it's for. -Kyz
Message msg ( "PlayerStateSet" );
msg . SetParam ( "PlayerNumber" , pPlayerState -> m_PlayerNumber );
HandleMessage ( msg );
2011-03-17 01:47:30 -04:00
}
2023-04-19 14:22:59 +02:00
void NoteField :: Load (
2011-03-17 01:47:30 -04:00
const NoteData * pNoteData ,
2014-12-07 01:53:17 -07:00
int iDrawDistanceAfterTargetsPixels ,
2011-03-17 01:47:30 -04:00
int iDrawDistanceBeforeTargetsPixels )
{
2019-06-22 12:35:38 -07:00
ASSERT ( pNoteData != nullptr );
2011-03-17 01:47:30 -04:00
m_pNoteData = pNoteData ;
m_iDrawDistanceAfterTargetsPixels = iDrawDistanceAfterTargetsPixels ;
m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels ;
ASSERT ( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels );
2015-01-01 04:36:20 -07:00
m_FieldRenderArgs . fail_fade = - 1 ;
2011-03-17 01:47:30 -04:00
//int i1 = m_pNoteData->GetNumTracks();
2014-12-07 01:53:17 -07:00
//int i2 = GAMESTATE->GetCurrentStyle(m_pPlayerState->m_PlayerNumber)->m_iColsPerPlayer;
ASSERT_M ( m_pNoteData -> GetNumTracks () == GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer ,
ssprintf ( "NumTracks %d = ColsPerPlayer %d" , m_pNoteData -> GetNumTracks (),
GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer ));
2016-06-17 16:23:08 -06:00
// If we're in routine mode, the noteskin is forcibly set to the routine
// noteskin metrics (which is bad in its own way). The noteskin set in the
// options is ignored and probably already set anyway. -Kyz
if ( GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_StyleType != StyleType_TwoPlayersSharedSides )
{
ensure_note_displays_have_skin ();
}
InitColumnRenderers ();
}
void NoteField :: ensure_note_displays_have_skin ()
{
2011-03-17 01:47:30 -04:00
// The NoteSkin may have changed at the beginning of a new course song.
RString sNoteSkinLower = m_pPlayerState -> m_PlayerOptions . GetCurrent (). m_sNoteSkin ;
/* XXX: Combination of good idea and bad idea to ensure courses load
* regardless of noteskin content. This may take a while to fix. */
NoteDisplayCols * badIdea = m_pCurDisplay ;
2011-11-27 22:20:32 -06:00
if ( sNoteSkinLower . empty ())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = m_pPlayerState -> m_PlayerOptions . GetPreferred (). m_sNoteSkin ;
2011-11-27 22:28:50 -06:00
if ( sNoteSkinLower . empty ())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = "default" ;
}
2022-07-10 18:28:56 +03:00
m_NoteDisplays . insert ( std :: pair < RString , NoteDisplayCols *> ( sNoteSkinLower , badIdea ));
2011-03-17 01:47:30 -04:00
}
sNoteSkinLower . MakeLower ();
2022-07-10 18:28:56 +03:00
std :: map < RString , NoteDisplayCols *>:: iterator it = m_NoteDisplays . find ( sNoteSkinLower );
2011-11-27 22:20:32 -06:00
ASSERT_M ( it != m_NoteDisplays . end (), ssprintf ( "iterator != m_NoteDisplays.end() [sNoteSkinLower = %s]" , sNoteSkinLower . c_str ()) );
2011-03-17 01:47:30 -04:00
memset ( m_pDisplays , 0 , sizeof ( m_pDisplays ) );
FOREACH_EnabledPlayer ( pn )
{
sNoteSkinLower = GAMESTATE -> m_pPlayerState [ pn ] -> m_PlayerOptions . GetCurrent (). m_sNoteSkin ;
// XXX: Re-setup sNoteSkinLower. Unsure if inserting the skin again is needed.
2011-11-27 22:28:50 -06:00
if ( sNoteSkinLower . empty ())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = GAMESTATE -> m_pPlayerState [ pn ] -> m_PlayerOptions . GetPreferred (). m_sNoteSkin ;
2011-11-27 22:28:50 -06:00
if ( sNoteSkinLower . empty ())
2011-03-17 01:47:30 -04:00
{
sNoteSkinLower = "default" ;
}
2022-07-10 18:28:56 +03:00
m_NoteDisplays . insert ( std :: pair < RString , NoteDisplayCols *> ( sNoteSkinLower , badIdea ));
2011-03-17 01:47:30 -04:00
}
2011-11-27 22:28:50 -06:00
2011-03-17 01:47:30 -04:00
sNoteSkinLower . MakeLower ();
it = m_NoteDisplays . find ( sNoteSkinLower );
ASSERT_M ( it != m_NoteDisplays . end (), sNoteSkinLower );
m_pDisplays [ pn ] = it -> second ;
}
2014-12-26 01:54:34 -07:00
}
void NoteField :: InitColumnRenderers ()
{
2015-01-01 04:36:20 -07:00
m_FieldRenderArgs . player_state = m_pPlayerState ;
m_FieldRenderArgs . reverse_offset_pixels = m_fYReverseOffsetPixels ;
m_FieldRenderArgs . receptor_row = & ( m_pCurDisplay -> m_ReceptorArrowRow );
m_FieldRenderArgs . ghost_row = & ( m_pCurDisplay -> m_GhostArrowRow );
m_FieldRenderArgs . note_data = m_pNoteData ;
2015-01-27 18:03:41 -07:00
m_ColumnRenderers . resize ( GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer );
2023-04-19 23:04:25 +02:00
for ( std :: size_t ncr = 0 ; ncr < m_ColumnRenderers . size (); ++ ncr )
2014-12-26 01:54:34 -07:00
{
FOREACH_EnabledPlayer ( pn )
{
m_ColumnRenderers [ ncr ]. m_displays [ pn ] = & ( m_pDisplays [ pn ] -> display [ ncr ]);
}
m_ColumnRenderers [ ncr ]. m_displays [ PLAYER_INVALID ] = & ( m_pCurDisplay -> display [ ncr ]);
m_ColumnRenderers [ ncr ]. m_column = ncr ;
2015-01-01 04:36:20 -07:00
m_ColumnRenderers [ ncr ]. m_column_render_args . column = ncr ;
m_ColumnRenderers [ ncr ]. m_field_render_args = & m_FieldRenderArgs ;
2014-12-26 01:54:34 -07:00
}
m_pCurDisplay -> m_ReceptorArrowRow . SetColumnRenderers ( m_ColumnRenderers );
m_pCurDisplay -> m_GhostArrowRow . SetColumnRenderers ( m_ColumnRenderers );
2011-03-17 01:47:30 -04:00
}
void NoteField :: Update ( float fDeltaTime )
{
if ( m_bFirstUpdate )
{
m_pCurDisplay -> m_ReceptorArrowRow . PlayCommand ( "On" );
}
ActorFrame :: Update ( fDeltaTime );
2015-04-24 15:55:54 -06:00
ArrowEffects :: SetCurrentOptions ( & m_pPlayerState -> m_PlayerOptions . GetCurrent ());
2011-03-17 01:47:30 -04:00
2023-04-19 23:04:25 +02:00
for ( std :: size_t c = 0 ; c < m_ColumnRenderers . size (); ++ c )
2014-12-26 01:54:34 -07:00
{
m_ColumnRenderers [ c ]. Update ( fDeltaTime );
}
2011-03-17 01:47:30 -04:00
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
2011-09-14 12:20:42 +07:00
const float fCurrentBeat = m_pPlayerState -> GetDisplayedPosition (). m_fSongBeat ;
2011-03-17 01:47:30 -04:00
bool bTweeningOn = m_sprBoard -> GetCurrentDiffuseAlpha () >= 0.98 && m_sprBoard -> GetCurrentDiffuseAlpha () < 1.00 ; // HACK
if ( ! bTweeningOn && m_fCurrentBeatLastUpdate != - 1 )
{
2015-04-24 15:55:54 -06:00
const float fYOffsetLast = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , m_fCurrentBeatLastUpdate );
2017-06-09 21:10:17 -07:00
const float fYPosLast = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fYOffsetLast , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
const float fPixelDifference = fYPosLast - m_fYPosCurrentBeatLastUpdate ;
//LOG->Trace( "speed = %f, %f, %f, %f, %f, %f", fSpeed, fYOffsetAtCurrent, fYOffsetAtNext, fSecondsAtCurrent, fSecondsAtNext, fPixelDifference, fSecondsDifference );
m_fBoardOffsetPixels += fPixelDifference ;
wrap ( m_fBoardOffsetPixels , m_sprBoard -> GetUnzoomedHeight () );
}
m_fCurrentBeatLastUpdate = fCurrentBeat ;
const float fYOffsetCurrent = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , m_fCurrentBeatLastUpdate );
2017-06-09 21:10:17 -07:00
m_fYPosCurrentBeatLastUpdate = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fYOffsetCurrent , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
m_rectMarkerBar . Update ( fDeltaTime );
NoteDisplayCols * cur = m_pCurDisplay ;
cur -> m_ReceptorArrowRow . Update ( fDeltaTime );
cur -> m_GhostArrowRow . Update ( fDeltaTime );
2015-01-01 04:36:20 -07:00
if ( m_FieldRenderArgs . fail_fade >= 0 )
2022-07-10 18:28:56 +03:00
m_FieldRenderArgs . fail_fade = std :: min ( m_FieldRenderArgs . fail_fade + fDeltaTime / FADE_FAIL_TIME , 1.0f );
2011-03-17 01:47:30 -04:00
// Update fade to failed
2015-01-01 04:36:20 -07:00
m_pCurDisplay -> m_ReceptorArrowRow . SetFadeToFailPercent ( m_FieldRenderArgs . fail_fade );
2011-03-17 01:47:30 -04:00
NoteDisplay :: Update ( fDeltaTime );
/* Update all NoteDisplays. Hack: We need to call this once per frame, not
* once per player. */
// TODO: Remove use of PlayerNumber.
PlayerNumber pn = m_pPlayerState -> m_PlayerNumber ;
2011-06-06 20:40:11 -04:00
if ( pn == GAMESTATE -> GetMasterPlayerNumber () )
2011-03-17 01:47:30 -04:00
NoteDisplay :: Update ( fDeltaTime );
}
float NoteField :: GetWidth () const
{
2014-12-07 01:53:17 -07:00
const Style * pStyle = GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber );
2011-03-17 01:47:30 -04:00
float fMinX , fMaxX ;
// TODO: Remove use of PlayerNumber.
pStyle -> GetMinAndMaxColX ( m_pPlayerState -> m_PlayerNumber , fMinX , fMaxX );
2017-06-09 21:10:17 -07:00
const float fYZoom = ArrowEffects :: GetZoom ( m_pPlayerState , 0 , 0 );
2011-03-17 01:47:30 -04:00
return ( fMaxX - fMinX + ARROW_SIZE ) * fYZoom ;
}
void NoteField :: DrawBeatBar ( const float fBeat , BeatBarType type , int iMeasureIndex )
{
bool bIsMeasure = type == measure ;
const float fYOffset = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , fBeat );
2017-06-09 21:10:17 -07:00
const float fYPos = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fYOffset , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
float fAlpha ;
int iState ;
if ( bIsMeasure )
{
2024-03-03 00:08:55 -08:00
fAlpha = m_fBarMeasureAlpha ;
2011-03-17 01:47:30 -04:00
iState = 0 ;
}
else
{
2015-12-04 00:28:22 -07:00
PlayerOptions const & curr_ops = m_pPlayerState -> m_PlayerOptions . GetCurrent ();
float fScrollSpeed = curr_ops . m_fScrollSpeed ;
if ( curr_ops . m_fTimeSpacing > 0 )
{
2011-03-17 01:47:30 -04:00
fScrollSpeed = 4 ;
2015-12-04 00:28:22 -07:00
}
else if ( curr_ops . m_fMaxScrollBPM != 0 )
{
fScrollSpeed = curr_ops . m_fMaxScrollBPM / m_pPlayerState -> m_fReadBPM ;
}
2011-03-17 01:47:30 -04:00
switch ( type )
{
DEFAULT_FAIL ( type );
case measure : // handled above
2023-04-19 20:55:30 +02:00
case beat :
2024-03-03 00:08:55 -08:00
fAlpha = m_fBar4thAlpha ;
2011-03-17 01:47:30 -04:00
iState = 1 ;
break ;
case half_beat :
2024-03-03 00:08:55 -08:00
fAlpha = SCALE ( fScrollSpeed , 1.0f , 2.0f , 0.0f , m_fBar8thAlpha );
2011-03-17 01:47:30 -04:00
iState = 2 ;
break ;
case quarter_beat :
2024-03-03 00:08:55 -08:00
fAlpha = SCALE ( fScrollSpeed , 2.0f , 4.0f , 0.0f , m_fBar16thAlpha );
2011-03-17 01:47:30 -04:00
iState = 3 ;
break ;
}
CLAMP ( fAlpha , 0 , 1 );
}
float fWidth = GetWidth ();
float fFrameWidth = m_sprBeatBars . GetUnzoomedWidth ();
m_sprBeatBars . SetX ( 0 );
m_sprBeatBars . SetY ( fYPos );
m_sprBeatBars . SetDiffuse ( RageColor ( 1 , 1 , 1 , fAlpha ) );
m_sprBeatBars . SetState ( iState );
m_sprBeatBars . SetCustomTextureRect ( RectF ( 0 , SCALE ( iState , 0.f , 4.f , 0.f , 1.f ), fWidth / fFrameWidth , SCALE ( iState + 1 , 0.f , 4.f , 0.f , 1.f )) );
m_sprBeatBars . SetZoomX ( fWidth / m_sprBeatBars . GetUnzoomedWidth () );
m_sprBeatBars . Draw ();
if ( GAMESTATE -> IsEditing () && bIsMeasure )
{
int iMeasureNoDisplay = iMeasureIndex ;
m_textMeasureNumber . SetDiffuse ( RageColor ( 1 , 1 , 1 , 1 ) );
m_textMeasureNumber . SetGlow ( RageColor ( 1 , 1 , 1 , 0 ) );
m_textMeasureNumber . SetHorizAlign ( align_right );
m_textMeasureNumber . SetText ( ssprintf ( "%d" , iMeasureNoDisplay ) );
m_textMeasureNumber . SetXY ( - fWidth / 2 , fYPos );
m_textMeasureNumber . Draw ();
}
}
void NoteField :: DrawBoard ( int iDrawDistanceAfterTargetsPixels , int iDrawDistanceBeforeTargetsPixels )
{
// todo: make this an AutoActor instead? -aj
Sprite * pSprite = dynamic_cast < Sprite *> ( ( Actor * ) m_sprBoard );
2019-06-22 12:35:38 -07:00
if ( pSprite == nullptr )
2011-03-17 01:47:30 -04:00
{
2014-05-02 17:01:38 -05:00
m_sprBoard -> Draw ();
}
else
{
// Draw the board centered on fYPosAt0 so that the board doesn't slide as
// the draw distance changes with modifiers.
2017-06-09 21:10:17 -07:00
const float fYPosAt0 = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , 0 , m_fYReverseOffsetPixels );
2014-05-02 17:01:38 -05:00
RectF rect = * pSprite -> GetCurrentTextureCoordRect ();
const float fBoardGraphicHeightPixels = pSprite -> GetUnzoomedHeight ();
float fTexCoordOffset = m_fBoardOffsetPixels / fBoardGraphicHeightPixels ;
2011-03-17 01:47:30 -04:00
// top half
const float fHeight = iDrawDistanceBeforeTargetsPixels - iDrawDistanceAfterTargetsPixels ;
const float fY = fYPosAt0 - (( iDrawDistanceBeforeTargetsPixels + iDrawDistanceAfterTargetsPixels ) / 2.0f );
pSprite -> ZoomToHeight ( fHeight );
pSprite -> SetY ( fY );
// handle tex coord offset and fade
rect . top = - fTexCoordOffset - ( iDrawDistanceBeforeTargetsPixels / fBoardGraphicHeightPixels );
rect . bottom = - fTexCoordOffset + ( - iDrawDistanceAfterTargetsPixels / fBoardGraphicHeightPixels );
pSprite -> SetCustomTextureRect ( rect );
float fFadeTop = FADE_BEFORE_TARGETS_PERCENT * iDrawDistanceBeforeTargetsPixels / ( iDrawDistanceBeforeTargetsPixels - iDrawDistanceAfterTargetsPixels );
pSprite -> SetFadeTop ( fFadeTop );
pSprite -> Draw ();
}
}
void NoteField :: DrawMarkerBar ( int iBeat )
{
float fBeat = NoteRowToBeat ( iBeat );
const float fYOffset = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , fBeat );
2017-06-09 21:10:17 -07:00
const float fYPos = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fYOffset , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
m_rectMarkerBar . StretchTo ( RectF ( - GetWidth () / 2 , fYPos - ARROW_SIZE / 2 , GetWidth () / 2 , fYPos + ARROW_SIZE / 2 ) );
m_rectMarkerBar . Draw ();
}
2011-08-05 12:14:15 -05:00
static ThemeMetric < RageColor > AREA_HIGHLIGHT_COLOR ( "NoteField" , "AreaHighlightColor" );
2011-03-17 01:47:30 -04:00
void NoteField :: DrawAreaHighlight ( int iStartBeat , int iEndBeat )
{
float fStartBeat = NoteRowToBeat ( iStartBeat );
float fEndBeat = NoteRowToBeat ( iEndBeat );
float fDrawDistanceAfterTargetsPixels = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , fStartBeat );
2017-06-09 21:10:17 -07:00
float fYStartPos = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fDrawDistanceAfterTargetsPixels , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
float fDrawDistanceBeforeTargetsPixels = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , fEndBeat );
2017-06-09 21:10:17 -07:00
float fYEndPos = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , fDrawDistanceBeforeTargetsPixels , m_fYReverseOffsetPixels );
2011-03-17 01:47:30 -04:00
// The caller should have clamped these to reasonable values
ASSERT ( fYStartPos > - 1000 );
ASSERT ( fYEndPos < + 5000 );
m_rectAreaHighlight . StretchTo ( RectF ( - GetWidth () / 2 , fYStartPos , GetWidth () / 2 , fYEndPos ) );
2011-08-05 12:14:15 -05:00
m_rectAreaHighlight . SetDiffuse ( AREA_HIGHLIGHT_COLOR );
2011-03-17 01:47:30 -04:00
m_rectAreaHighlight . Draw ();
}
// todo: add DrawWarpAreaBG? -aj
static ThemeMetric < RageColor > BPM_COLOR ( "NoteField" , "BPMColor" );
static ThemeMetric < RageColor > STOP_COLOR ( "NoteField" , "StopColor" );
static ThemeMetric < RageColor > DELAY_COLOR ( "NoteField" , "DelayColor" );
2011-03-24 20:41:35 -04:00
static ThemeMetric < RageColor > WARP_COLOR ( "NoteField" , "WarpColor" );
2015-04-24 15:55:54 -06:00
static ThemeMetric < RageColor > TIME_SIG_COLOR ( "NoteField" , "TimeSignatureColor" );
2011-03-17 01:47:30 -04:00
static ThemeMetric < RageColor > TICKCOUNT_COLOR ( "NoteField" , "TickcountColor" );
static ThemeMetric < RageColor > COMBO_COLOR ( "NoteField" , "ComboColor" );
2011-04-05 00:35:45 -04:00
static ThemeMetric < RageColor > LABEL_COLOR ( "NoteField" , "LabelColor" );
2011-05-15 15:37:46 -04:00
static ThemeMetric < RageColor > SPEED_COLOR ( "NoteField" , "SpeedColor" );
2011-05-25 22:44:41 +07:00
static ThemeMetric < RageColor > SCROLL_COLOR ( "NoteField" , "ScrollColor" );
2011-05-16 12:02:17 -04:00
static ThemeMetric < RageColor > FAKE_COLOR ( "NoteField" , "FakeColor" );
2011-03-17 01:47:30 -04:00
static ThemeMetric < bool > BPM_IS_LEFT_SIDE ( "NoteField" , "BPMIsLeftSide" );
static ThemeMetric < bool > STOP_IS_LEFT_SIDE ( "NoteField" , "StopIsLeftSide" );
static ThemeMetric < bool > DELAY_IS_LEFT_SIDE ( "NoteField" , "DelayIsLeftSide" );
2011-03-24 20:41:35 -04:00
static ThemeMetric < bool > WARP_IS_LEFT_SIDE ( "NoteField" , "WarpIsLeftSide" );
2015-04-24 15:55:54 -06:00
static ThemeMetric < bool > TIME_SIG_IS_LEFT_SIDE ( "NoteField" , "TimeSignatureIsLeftSide" );
2011-03-17 01:47:30 -04:00
static ThemeMetric < bool > TICKCOUNT_IS_LEFT_SIDE ( "NoteField" , "TickcountIsLeftSide" );
static ThemeMetric < bool > COMBO_IS_LEFT_SIDE ( "NoteField" , "ComboIsLeftSide" );
2011-04-05 00:35:45 -04:00
static ThemeMetric < bool > LABEL_IS_LEFT_SIDE ( "NoteField" , "LabelIsLeftSide" );
2011-05-15 15:37:46 -04:00
static ThemeMetric < bool > SPEED_IS_LEFT_SIDE ( "NoteField" , "SpeedIsLeftSide" );
2011-05-25 22:44:41 +07:00
static ThemeMetric < bool > SCROLL_IS_LEFT_SIDE ( "NoteField" , "ScrollIsLeftSide" );
2011-05-16 12:02:17 -04:00
static ThemeMetric < bool > FAKE_IS_LEFT_SIDE ( "NoteField" , "FakeIsLeftSide" );
2011-03-17 01:47:30 -04:00
static ThemeMetric < float > BPM_OFFSETX ( "NoteField" , "BPMOffsetX" );
static ThemeMetric < float > STOP_OFFSETX ( "NoteField" , "StopOffsetX" );
static ThemeMetric < float > DELAY_OFFSETX ( "NoteField" , "DelayOffsetX" );
2011-03-24 20:41:35 -04:00
static ThemeMetric < float > WARP_OFFSETX ( "NoteField" , "WarpOffsetX" );
2015-04-24 15:55:54 -06:00
static ThemeMetric < float > TIME_SIG_OFFSETX ( "NoteField" , "TimeSignatureOffsetX" );
2011-03-17 01:47:30 -04:00
static ThemeMetric < float > TICKCOUNT_OFFSETX ( "NoteField" , "TickcountOffsetX" );
static ThemeMetric < float > COMBO_OFFSETX ( "NoteField" , "ComboOffsetX" );
2011-04-05 00:35:45 -04:00
static ThemeMetric < float > LABEL_OFFSETX ( "NoteField" , "LabelOffsetX" );
2011-05-15 15:37:46 -04:00
static ThemeMetric < float > SPEED_OFFSETX ( "NoteField" , "SpeedOffsetX" );
2011-05-25 22:44:41 +07:00
static ThemeMetric < float > SCROLL_OFFSETX ( "NoteField" , "ScrollOffsetX" );
2011-05-16 12:02:17 -04:00
static ThemeMetric < float > FAKE_OFFSETX ( "NoteField" , "FakeOffsetX" );
2011-05-13 14:14:57 +07:00
2015-04-24 15:55:54 -06:00
void NoteField :: set_text_measure_number_for_draw (
const float beat , const float side_sign , float x_offset ,
const float horiz_align , const RageColor & color , const RageColor & glow )
2011-03-17 01:47:30 -04:00
{
2015-04-24 15:55:54 -06:00
const float y_offset = ArrowEffects :: GetYOffset ( m_pPlayerState , 0 , beat );
2017-06-09 21:10:17 -07:00
const float y_pos = ArrowEffects :: GetYPos ( m_pPlayerState , 0 , y_offset , m_fYReverseOffsetPixels );
const float zoom = ArrowEffects :: GetZoom ( m_pPlayerState , y_offset , 0 );
2015-04-24 15:55:54 -06:00
const float x_base = GetWidth () * .5f ;
x_offset *= zoom ;
2011-03-17 01:47:30 -04:00
2015-04-24 15:55:54 -06:00
m_textMeasureNumber . SetZoom ( zoom );
m_textMeasureNumber . SetHorizAlign ( horiz_align );
m_textMeasureNumber . SetDiffuse ( color );
m_textMeasureNumber . SetGlow ( glow );
m_textMeasureNumber . SetXY (( x_offset + x_base ) * side_sign , y_pos );
}
void NoteField :: draw_timing_segment_text ( const RString & text ,
const float beat , const float side_sign , float x_offset ,
const float horiz_align , const RageColor & color , const RageColor & glow )
{
set_text_measure_number_for_draw ( beat , side_sign , x_offset , horiz_align ,
color , glow );
m_textMeasureNumber . SetText ( text );
2011-03-17 01:47:30 -04:00
m_textMeasureNumber . Draw ();
}
2015-04-24 15:55:54 -06:00
void NoteField :: DrawAttackText ( const float beat , const Attack & attack ,
const RageColor & glow )
2011-03-17 01:47:30 -04:00
{
2015-04-24 15:55:54 -06:00
set_text_measure_number_for_draw ( beat , 1 , 10 , align_left ,
RageColor ( 0 , 0.8f , 0.8f , 1 ), glow );
2011-03-17 01:47:30 -04:00
m_textMeasureNumber . SetText ( attack . GetTextDescription () );
m_textMeasureNumber . Draw ();
}
2015-04-24 15:55:54 -06:00
void NoteField :: DrawBGChangeText ( const float beat , const RString new_bg_name ,
const RageColor & glow )
2011-03-17 01:47:30 -04:00
{
2015-04-24 15:55:54 -06:00
set_text_measure_number_for_draw ( beat , 1 , 0 , align_left , RageColor ( 0 , 1 , 0 , 1 ),
glow );
m_textMeasureNumber . SetText ( new_bg_name );
2011-03-17 01:47:30 -04:00
m_textMeasureNumber . Draw ();
}
2011-11-11 18:40:34 +07:00
static CacheNoteStat GetNumNotesFromBeginning ( const PlayerState * pPlayerState , float beat )
2011-09-19 12:50:18 +07:00
{
// XXX: I realized that I have copied and pasted my binary search code 3 times already.
// how can we abstract this?
2022-07-10 18:28:56 +03:00
const std :: vector < CacheNoteStat > & data = pPlayerState -> m_CacheNoteStat ;
2011-09-19 12:50:18 +07:00
int max = data . size () - 1 ;
int l = 0 , r = max ;
while ( l <= r )
{
int m = ( l + r ) / 2 ;
if ( ( m == 0 || data [ m ]. beat <= beat ) && ( m == max || beat < data [ m + 1 ]. beat ) )
{
return data [ m ];
}
else if ( data [ m ]. beat <= beat )
{
l = m + 1 ;
}
else
{
r = m - 1 ;
}
}
CacheNoteStat dummy = { 0 , 0 , 0 };
return dummy ;
}
2011-11-11 18:40:34 +07:00
static int GetNumNotesRange ( const PlayerState * pPlayerState , float fLow , float fHigh )
2011-09-19 12:50:18 +07:00
{
CacheNoteStat low = GetNumNotesFromBeginning ( pPlayerState , fLow );
CacheNoteStat high = GetNumNotesFromBeginning ( pPlayerState , fHigh );
return high . notesUpper - low . notesLower ;
}
2011-03-17 01:47:30 -04:00
float FindFirstDisplayedBeat ( const PlayerState * pPlayerState , int iDrawDistanceAfterTargetsPixels )
{
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
float fLow = 0 , fHigh = pPlayerState -> GetDisplayedPosition (). m_fSongBeat ;
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
bool bHasCache = pPlayerState -> m_CacheNoteStat . size () > 0 ;
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
if ( ! bHasCache )
2011-03-17 01:47:30 -04:00
{
2011-09-19 12:50:18 +07:00
fLow = fHigh - 4.0f ;
2011-03-17 01:47:30 -04:00
}
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
const int NUM_ITERATIONS = 24 ;
2011-11-11 18:40:34 +07:00
const int MAX_NOTES_AFTER = 64 ;
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
float fFirstBeatToDraw = fLow ;
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
for ( int i = 0 ; i < NUM_ITERATIONS ; i ++ )
2011-03-17 01:47:30 -04:00
{
2023-04-19 14:22:59 +02:00
2011-09-19 12:50:18 +07:00
float fMid = ( fLow + fHigh ) / 2.0f ;
2023-04-19 14:22:59 +02:00
2011-03-17 01:47:30 -04:00
bool bIsPastPeakYOffset ;
float fPeakYOffset ;
2011-09-19 12:50:18 +07:00
float fYOffset = ArrowEffects :: GetYOffset ( pPlayerState , 0 , fMid , fPeakYOffset , bIsPastPeakYOffset , true );
2011-03-17 01:47:30 -04:00
2011-09-19 12:50:18 +07:00
if ( fYOffset < iDrawDistanceAfterTargetsPixels || ( bHasCache && GetNumNotesRange ( pPlayerState , fMid , pPlayerState -> GetDisplayedPosition (). m_fSongBeat ) > MAX_NOTES_AFTER ) ) // off screen / too many notes
{
fFirstBeatToDraw = fMid ; // move towards fSongBeat
fLow = fMid ;
}
else // on screen, move away!!
{
fHigh = fMid ;
}
2023-04-19 14:22:59 +02:00
2011-03-17 01:47:30 -04:00
}
2011-09-19 12:50:18 +07:00
2011-03-17 01:47:30 -04:00
return fFirstBeatToDraw ;
2011-09-19 12:50:18 +07:00
2011-03-17 01:47:30 -04:00
}
float FindLastDisplayedBeat ( const PlayerState * pPlayerState , int iDrawDistanceBeforeTargetsPixels )
{
// Probe for last note to draw. Worst case is 0.25x + boost.
// Adjust search distance so that notes don't pop onto the screen.
float fSearchDistance = 10 ;
2011-09-14 12:20:42 +07:00
float fLastBeatToDraw = pPlayerState -> GetDisplayedPosition (). m_fSongBeat + fSearchDistance ;
float fSpeedMultiplier = pPlayerState -> GetDisplayedTiming (). GetDisplayedSpeedPercent ( pPlayerState -> GetDisplayedPosition (). m_fSongBeatVisible , pPlayerState -> GetDisplayedPosition (). m_fMusicSecondsVisible );
2011-03-17 01:47:30 -04:00
const int NUM_ITERATIONS = 20 ;
bool bBoomerang ;
{
2022-12-23 12:34:02 -07:00
const float * fAccels = pPlayerState -> m_PlayerOptions . GetCurrent (). m_fAccels ;
2011-03-17 01:47:30 -04:00
bBoomerang = ( fAccels [ PlayerOptions :: ACCEL_BOOMERANG ] != 0 );
}
for ( int i = 0 ; i < NUM_ITERATIONS ; i ++ )
{
bool bIsPastPeakYOffset ;
float fPeakYOffset ;
float fYOffset = ArrowEffects :: GetYOffset ( pPlayerState , 0 , fLastBeatToDraw , fPeakYOffset , bIsPastPeakYOffset , true );
if ( bBoomerang && ! bIsPastPeakYOffset )
fLastBeatToDraw += fSearchDistance ;
else if ( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
fLastBeatToDraw -= fSearchDistance ;
else // on screen
fLastBeatToDraw += fSearchDistance ;
fSearchDistance /= 2 ;
}
2011-05-24 12:44:48 +07:00
if ( fSpeedMultiplier < 0.75 )
{
2022-07-10 18:28:56 +03:00
fLastBeatToDraw = std :: min ( fLastBeatToDraw , pPlayerState -> GetDisplayedPosition (). m_fSongBeat + 16 );
2011-05-24 12:44:48 +07:00
}
2011-03-17 01:47:30 -04:00
return fLastBeatToDraw ;
}
bool NoteField :: IsOnScreen ( float fBeat , int iCol , int iDrawDistanceAfterTargetsPixels , int iDrawDistanceBeforeTargetsPixels ) const
{
2014-12-25 05:02:28 -07:00
// IMPORTANT: Do not modify this function without also modifying the
// version that is in NoteDisplay.cpp or coming up with a good way to
// merge them. -Kyz
2023-04-19 14:22:59 +02:00
// TRICKY: If boomerang is on, then ones in the range
2011-03-17 01:47:30 -04:00
// [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
// Test to see if this beat is visible before drawing.
float fYOffset = ArrowEffects :: GetYOffset ( m_pPlayerState , iCol , fBeat );
if ( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
return false ;
if ( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen
return false ;
return true ;
}
2015-04-03 14:19:28 -06:00
void NoteField :: CalcPixelsBeforeAndAfterTargets ()
{
const PlayerOptions & curr_options = m_pPlayerState -> m_PlayerOptions . GetCurrent ();
// Adjust draw range depending on some effects
2017-06-09 21:10:17 -07:00
m_FieldRenderArgs . draw_pixels_after_targets = m_iDrawDistanceAfterTargetsPixels * ( 1.f + curr_options . m_fDrawSizeBack );
2023-04-19 14:22:59 +02:00
// HACK: If boomerang and centered are on, then we want to draw much
2015-04-03 14:19:28 -06:00
// earlier so that the notes don't pop on screen.
float centered_times_boomerang =
curr_options . m_fScrolls [ PlayerOptions :: SCROLL_CENTERED ] *
curr_options . m_fAccels [ PlayerOptions :: ACCEL_BOOMERANG ];
m_FieldRenderArgs . draw_pixels_after_targets +=
int ( SCALE ( centered_times_boomerang , 0.f , 1.f , 0.f , - SCREEN_HEIGHT / 2 ));
m_FieldRenderArgs . draw_pixels_before_targets =
2017-06-09 21:10:17 -07:00
m_iDrawDistanceBeforeTargetsPixels * ( 1.f + curr_options . m_fDrawSize );
2015-04-03 14:19:28 -06:00
float draw_scale = 1 ;
2023-04-19 14:22:59 +02:00
draw_scale *= 1 + 0.5f * std :: abs ( curr_options . m_fPerspectiveTilt );
draw_scale *= 1 + std :: abs ( curr_options . m_fEffects [ PlayerOptions :: EFFECT_MINI ]);
2015-04-03 14:19:28 -06:00
m_FieldRenderArgs . draw_pixels_after_targets =
( int )( m_FieldRenderArgs . draw_pixels_after_targets * draw_scale );
m_FieldRenderArgs . draw_pixels_before_targets =
( int )( m_FieldRenderArgs . draw_pixels_before_targets * draw_scale );
}
2011-03-17 01:47:30 -04:00
void NoteField :: DrawPrimitives ()
{
//LOG->Trace( "NoteField::DrawPrimitives()" );
// This should be filled in on the first update.
2019-06-22 12:35:38 -07:00
ASSERT ( m_pCurDisplay != nullptr );
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
// ArrowEffects::Update call moved because having it happen once per
// NoteField (which means twice in two player) seemed wasteful. -Kyz
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
if ( m_drawing_board_primitive )
{
CalcPixelsBeforeAndAfterTargets ();
DrawBoard ( m_FieldRenderArgs . draw_pixels_after_targets ,
m_FieldRenderArgs . draw_pixels_before_targets );
return ;
}
// Some might prefer an else block, instead of returning from the if, but I
// don't want to bump the indent on the entire remaining section. -Kyz
2015-05-02 18:53:00 -06:00
ArrowEffects :: SetCurrentOptions ( & m_pPlayerState -> m_PlayerOptions . GetCurrent ());
2015-04-03 14:19:28 -06:00
CalcPixelsBeforeAndAfterTargets ();
2011-03-17 01:47:30 -04:00
NoteDisplayCols * cur = m_pCurDisplay ;
// Probe for first and last notes on the screen
2015-04-03 14:19:28 -06:00
float first_beat_to_draw = FindFirstDisplayedBeat (
m_pPlayerState , m_FieldRenderArgs . draw_pixels_after_targets );
float last_beat_to_draw = FindLastDisplayedBeat (
m_pPlayerState , m_FieldRenderArgs . draw_pixels_before_targets );
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
m_pPlayerState -> m_fLastDrawnBeat = last_beat_to_draw ;
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
m_FieldRenderArgs . first_row = BeatToNoteRow ( first_beat_to_draw );
m_FieldRenderArgs . last_row = BeatToNoteRow ( last_beat_to_draw );
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
//LOG->Trace( "start = %f.1, end = %f.1", first_beat_to_draw-fSongBeat, last_beat_to_draw-fSongBeat );
2015-01-01 04:36:20 -07:00
//LOG->Trace( "Drawing elements %d through %d", m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row );
2011-03-17 01:47:30 -04:00
2015-04-03 14:19:28 -06:00
#define IS_ON_SCREEN(fBeat) (first_beat_to_draw <= (fBeat) && (fBeat) <= last_beat_to_draw && IsOnScreen(fBeat, 0, m_FieldRenderArgs.draw_pixels_after_targets, m_FieldRenderArgs.draw_pixels_before_targets))
2011-03-17 01:47:30 -04:00
// Draw Receptors
{
cur -> m_ReceptorArrowRow . Draw ();
}
2011-09-14 12:20:42 +07:00
const TimingData * pTiming = & m_pPlayerState -> GetDisplayedTiming ();
2022-07-10 18:28:56 +03:00
const std :: vector < TimingSegment *>* segs [ NUM_TimingSegmentType ];
2011-09-15 03:28:58 +00:00
FOREACH_TimingSegmentType ( tst )
segs [ tst ] = & ( pTiming -> GetTimingSegments ( tst ));
2011-03-17 01:47:30 -04:00
// Draw beat bars
2024-02-15 14:37:56 -06:00
if ( ( GAMESTATE -> IsEditing () || m_bShowBeatBars ) && pTiming != nullptr )
2011-03-17 01:47:30 -04:00
{
2022-07-10 18:28:56 +03:00
const std :: vector < TimingSegment *> & tSigs = * segs [ SEGMENT_TIME_SIG ];
2011-03-17 01:47:30 -04:00
int iMeasureIndex = 0 ;
2023-04-19 23:04:25 +02:00
for ( std :: size_t i = 0 ; i < tSigs . size (); i ++ )
2011-03-17 01:47:30 -04:00
{
2011-09-15 03:28:58 +00:00
const TimeSignatureSegment * ts = ToTimeSignature ( tSigs [ i ]);
2015-01-01 04:36:20 -07:00
int iSegmentEndRow = ( i + 1 == tSigs . size ()) ? m_FieldRenderArgs . last_row : tSigs [ i + 1 ] -> GetRow ();
2011-08-24 12:54:30 -05:00
2011-03-17 01:47:30 -04:00
// beat bars every 16th note
2011-07-14 14:33:07 -04:00
int iDrawBeatBarsEveryRows = BeatToNoteRow ( (( float ) ts -> GetDen ()) / 4 ) / 4 ;
2011-03-17 01:47:30 -04:00
// In 4/4, every 16th beat bar is a measure
2011-07-14 14:33:07 -04:00
int iMeasureBarFrequency = ts -> GetNum () * 4 ;
2011-03-17 01:47:30 -04:00
int iBeatBarsDrawn = 0 ;
2011-07-14 14:33:07 -04:00
for ( int j = ts -> GetRow (); j < iSegmentEndRow ; j += iDrawBeatBarsEveryRows )
2011-03-17 01:47:30 -04:00
{
bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0 ;
BeatBarType type = quarter_beat ;
if ( bMeasureBar )
type = measure ;
else if ( iBeatBarsDrawn % 4 == 0 )
type = beat ;
else if ( iBeatBarsDrawn % 2 == 0 )
type = half_beat ;
2011-07-14 14:33:07 -04:00
float fBeat = NoteRowToBeat ( j );
2011-03-17 01:47:30 -04:00
if ( IS_ON_SCREEN ( fBeat ) )
{
DrawBeatBar ( fBeat , type , iMeasureIndex );
}
iBeatBarsDrawn ++ ;
if ( bMeasureBar )
iMeasureIndex ++ ;
}
}
}
2019-06-22 12:35:38 -07:00
if ( GAMESTATE -> IsEditing () && pTiming != nullptr )
2011-03-17 01:47:30 -04:00
{
2019-06-22 12:35:38 -07:00
ASSERT ( GAMESTATE -> m_pCurSong != nullptr );
2011-03-17 01:47:30 -04:00
2011-05-13 14:14:57 +07:00
const TimingData & timing = * pTiming ;
2016-02-07 21:04:10 -07:00
const RageColor text_glow = RageColor ( 1 , 1 , 1 , RageFastCos ( RageTimer :: GetTimeSinceStartFast () * 2 ) / 2 + 0.5f );
2011-08-24 12:54:30 -05:00
2015-04-24 15:55:54 -06:00
float horiz_align = align_right ;
float side_sign = 1 ;
#define draw_all_segments(str_exp, name, caps_name) \
horiz_align= caps_name##_IS_LEFT_SIDE ? align_right : align_left; \
side_sign= caps_name##_IS_LEFT_SIDE ? -1 : 1; \
2023-04-19 23:04:25 +02:00
for(std::size_t i= 0; i < segs[SEGMENT_##caps_name]->size(); ++i) \
2015-04-24 15:55:54 -06:00
{ \
const name##Segment* seg= To##name((*segs[SEGMENT_##caps_name])[i]); \
if(seg->GetRow() >= m_FieldRenderArgs.first_row && \
seg->GetRow() <= m_FieldRenderArgs.last_row && \
IS_ON_SCREEN(seg->GetBeat())) \
{ \
2015-05-10 01:04:06 -06:00
draw_timing_segment_text(str_exp, seg->GetBeat(), side_sign, \
2015-04-24 15:55:54 -06:00
caps_name##_OFFSETX, horiz_align, caps_name##_COLOR, text_glow); \
} \
2011-06-01 12:15:13 -04:00
}
2011-08-24 12:54:30 -05:00
2019-06-22 12:35:38 -07:00
draw_all_segments ( std :: to_string ( seg -> GetRatio ()), Scroll , SCROLL );
draw_all_segments ( std :: to_string ( seg -> GetBPM ()), BPM , BPM );
draw_all_segments ( std :: to_string ( seg -> GetPause ()), Stop , STOP );
draw_all_segments ( std :: to_string ( seg -> GetPause ()), Delay , DELAY );
draw_all_segments ( std :: to_string ( seg -> GetLength ()), Warp , WARP );
2015-04-24 15:55:54 -06:00
draw_all_segments ( ssprintf ( "%d \n -- \n %d" , seg -> GetNum (), seg -> GetDen ()),
TimeSignature , TIME_SIG );
draw_all_segments ( ssprintf ( "%d" , seg -> GetTicks ()), Tickcount , TICKCOUNT );
draw_all_segments (
ssprintf ( "%d/%d" , seg -> GetCombo (), seg -> GetMissCombo ()), Combo , COMBO );
draw_all_segments ( seg -> GetLabel (), Label , LABEL );
draw_all_segments ( ssprintf ( "%s \n %s \n %s" ,
2019-06-22 12:35:38 -07:00
std :: to_string ( seg -> GetRatio ()). c_str (),
2015-04-24 15:55:54 -06:00
( seg -> GetUnit () == 1 ? "S" : "B" ),
2019-06-22 12:35:38 -07:00
std :: to_string ( seg -> GetDelay ()). c_str ()), Speed , SPEED );
draw_all_segments ( std :: to_string ( seg -> GetLength ()), Fake , FAKE );
2015-04-24 15:55:54 -06:00
#undef draw_all_segments
2011-03-17 01:47:30 -04:00
// Course mods text
const Course * pCourse = GAMESTATE -> m_pCurCourse ;
if ( pCourse )
{
2023-04-19 14:22:59 +02:00
ASSERT_M ( GAMESTATE -> m_iEditCourseEntryIndex >= 0 && GAMESTATE -> m_iEditCourseEntryIndex < ( int ) pCourse -> m_vEntries . size (),
2011-03-17 01:47:30 -04:00
ssprintf ( "%i" , GAMESTATE -> m_iEditCourseEntryIndex . Get ()) );
const CourseEntry & ce = pCourse -> m_vEntries [ GAMESTATE -> m_iEditCourseEntryIndex ];
2019-06-22 12:35:38 -07:00
for ( Attack const & a : ce . attacks )
2011-03-17 01:47:30 -04:00
{
2019-06-22 12:35:38 -07:00
float fSecond = a . fStartSecond ;
2011-05-09 21:19:40 -04:00
float fBeat = timing . GetBeatFromElapsedTime ( fSecond );
2011-03-17 01:47:30 -04:00
2015-01-01 04:36:20 -07:00
if ( BeatToNoteRow ( fBeat ) >= m_FieldRenderArgs . first_row &&
2015-04-24 15:55:54 -06:00
BeatToNoteRow ( fBeat ) <= m_FieldRenderArgs . last_row &&
IS_ON_SCREEN ( fBeat ))
2011-03-17 01:47:30 -04:00
{
2019-06-22 12:35:38 -07:00
DrawAttackText ( fBeat , a , text_glow );
2011-03-17 01:47:30 -04:00
}
}
}
2011-06-24 18:07:47 -04:00
else
{
AttackArray & attacks = GAMESTATE -> m_bIsUsingStepTiming ?
GAMESTATE -> m_pCurSteps [ PLAYER_1 ] -> m_Attacks :
GAMESTATE -> m_pCurSong -> m_Attacks ;
2022-12-23 12:34:02 -07:00
for ( const Attack & a : attacks )
2011-06-24 18:07:47 -04:00
{
2022-11-08 06:59:56 -07:00
float fBeat = timing . GetBeatFromElapsedTime ( a . fStartSecond );
if ( BeatToNoteRow ( fBeat ) >= m_FieldRenderArgs . first_row &&
BeatToNoteRow ( fBeat ) <= m_FieldRenderArgs . last_row &&
IS_ON_SCREEN ( fBeat ))
2011-06-24 18:07:47 -04:00
{
2022-11-08 06:59:56 -07:00
this -> DrawAttackText ( fBeat , a , text_glow );
2011-06-24 18:07:47 -04:00
}
}
}
2011-08-24 12:54:30 -05:00
2011-05-17 00:07:43 -04:00
if ( ! GAMESTATE -> m_bIsUsingStepTiming )
2011-03-17 01:47:30 -04:00
{
2011-05-12 22:34:16 -04:00
// BGChange text
2012-12-27 16:59:35 -05:00
EditMode mode = GAMESTATE -> m_EditMode ;
switch ( mode )
2011-05-12 22:34:16 -04:00
{
case EditMode_Home :
case EditMode_CourseMods :
case EditMode_Practice :
break ;
case EditMode_Full :
2011-03-17 01:47:30 -04:00
{
2022-07-10 18:28:56 +03:00
std :: vector < BackgroundChange >:: iterator iter [ NUM_BackgroundLayer ];
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer ( j )
iter [ j ] = GAMESTATE -> m_pCurSong -> GetBackgroundChanges ( j ). begin ();
2011-08-24 12:54:30 -05:00
2016-03-27 21:52:05 -04:00
for (;;)
2011-03-17 01:47:30 -04:00
{
2011-05-12 22:34:16 -04:00
float fLowestBeat = FLT_MAX ;
2022-07-10 18:28:56 +03:00
std :: vector < BackgroundLayer > viLowestIndex ;
2011-08-24 12:54:30 -05:00
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer ( j )
2011-03-17 01:47:30 -04:00
{
2011-07-14 14:33:07 -04:00
if ( iter [ j ] == GAMESTATE -> m_pCurSong -> GetBackgroundChanges ( j ). end () )
2016-03-27 21:52:05 -04:00
{
2011-05-12 22:34:16 -04:00
continue ;
2016-03-27 21:52:05 -04:00
}
2011-07-14 14:33:07 -04:00
float fBeat = iter [ j ] -> m_fStartBeat ;
2011-05-12 22:34:16 -04:00
if ( fBeat < fLowestBeat )
{
fLowestBeat = fBeat ;
viLowestIndex . clear ();
2011-07-14 14:33:07 -04:00
viLowestIndex . push_back ( j );
2011-05-12 22:34:16 -04:00
}
else if ( fBeat == fLowestBeat )
{
2011-07-14 14:33:07 -04:00
viLowestIndex . push_back ( j );
2011-05-12 22:34:16 -04:00
}
2011-03-17 01:47:30 -04:00
}
2023-04-19 14:22:59 +02:00
2011-05-12 22:34:16 -04:00
if ( viLowestIndex . empty () )
{
2011-07-14 14:33:07 -04:00
FOREACH_BackgroundLayer ( j )
2016-03-27 21:52:05 -04:00
{
2011-07-14 14:33:07 -04:00
ASSERT ( iter [ j ] == GAMESTATE -> m_pCurSong -> GetBackgroundChanges ( j ). end () );
2016-03-27 21:52:05 -04:00
}
2011-05-12 22:34:16 -04:00
break ;
}
2023-04-19 14:22:59 +02:00
2011-05-12 22:34:16 -04:00
if ( IS_ON_SCREEN ( fLowestBeat ) )
{
2022-07-10 18:28:56 +03:00
std :: vector < RString > vsBGChanges ;
2019-06-22 12:35:38 -07:00
for ( BackgroundLayer const & bl : viLowestIndex )
2011-05-12 22:34:16 -04:00
{
2019-06-22 12:35:38 -07:00
ASSERT ( iter [ bl ] != GAMESTATE -> m_pCurSong -> GetBackgroundChanges ( bl ). end () );
const BackgroundChange & change = * iter [ bl ];
2011-05-12 22:34:16 -04:00
RString s = change . GetTextDescription ();
2019-06-22 12:35:38 -07:00
if ( bl != 0 )
2016-03-27 21:52:05 -04:00
{
2019-06-22 12:35:38 -07:00
s = ssprintf ( "%d: " , bl ) + s ;
2016-03-27 21:52:05 -04:00
}
2011-05-12 22:34:16 -04:00
vsBGChanges . push_back ( s );
}
2015-04-24 15:55:54 -06:00
DrawBGChangeText ( fLowestBeat , join ( " \n " , vsBGChanges ), text_glow );
2011-05-12 22:34:16 -04:00
}
2019-06-22 12:35:38 -07:00
for ( BackgroundLayer const & bl : viLowestIndex )
2016-03-27 21:52:05 -04:00
{
2019-06-22 12:35:38 -07:00
iter [ bl ] ++ ;
2016-03-27 21:52:05 -04:00
}
2011-03-17 01:47:30 -04:00
}
}
2011-05-12 22:34:16 -04:00
break ;
default :
2012-12-27 16:59:35 -05:00
FAIL_M ( ssprintf ( "Invalid edit mode: %i" , mode ));
2011-05-12 22:34:16 -04:00
}
2011-03-17 01:47:30 -04:00
}
// Draw marker bars
if ( m_iBeginMarker != - 1 && m_iEndMarker != - 1 )
{
int iBegin = m_iBeginMarker ;
int iEnd = m_iEndMarker ;
2015-01-01 04:36:20 -07:00
CLAMP ( iBegin , m_FieldRenderArgs . first_row , m_FieldRenderArgs . last_row );
CLAMP ( iEnd , m_FieldRenderArgs . first_row , m_FieldRenderArgs . last_row );
2011-03-17 01:47:30 -04:00
DrawAreaHighlight ( iBegin , iEnd );
}
else if ( m_iBeginMarker != - 1 )
{
2015-01-01 04:36:20 -07:00
if ( m_iBeginMarker >= m_FieldRenderArgs . first_row &&
m_iBeginMarker <= m_FieldRenderArgs . last_row )
2011-03-17 01:47:30 -04:00
DrawMarkerBar ( m_iBeginMarker );
}
else if ( m_iEndMarker != - 1 )
{
2015-01-01 04:36:20 -07:00
if ( m_iEndMarker >= m_FieldRenderArgs . first_row &&
m_iEndMarker <= m_FieldRenderArgs . last_row )
2011-03-17 01:47:30 -04:00
DrawMarkerBar ( m_iEndMarker );
}
}
// Optimization is very important here because there are so many arrows to draw.
// Draw the arrows in order of column. This minimizes texture switches and
// lets us draw in big batches.
2014-12-07 01:53:17 -07:00
const Style * pStyle = GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber );
2023-04-19 14:22:59 +02:00
ASSERT_M ( m_pNoteData -> GetNumTracks () == GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer ,
ssprintf ( "NumTracks %d != ColsPerPlayer %d" , m_pNoteData -> GetNumTracks (),
2015-01-27 18:03:41 -07:00
GAMESTATE -> GetCurrentStyle ( m_pPlayerState -> m_PlayerNumber ) -> m_iColsPerPlayer ));
2011-03-17 01:47:30 -04:00
2015-04-24 15:55:54 -06:00
if ( * m_FieldRenderArgs . selection_begin_marker != - 1 &&
* m_FieldRenderArgs . selection_end_marker != - 1 )
{
m_FieldRenderArgs . selection_glow = SCALE (
2016-02-07 21:04:10 -07:00
RageFastCos ( RageTimer :: GetTimeSinceStartFast () * 2 ), - 1 , 1 , 0.1f , 0.3f );
2015-04-24 15:55:54 -06:00
}
2015-01-01 04:36:20 -07:00
m_FieldRenderArgs . fade_before_targets = FADE_BEFORE_TARGETS_PERCENT ;
2014-12-26 01:54:34 -07:00
2011-07-14 14:33:07 -04:00
for ( int j = 0 ; j < m_pNoteData -> GetNumTracks (); j ++ ) // for each arrow column
2011-03-17 01:47:30 -04:00
{
2011-07-14 14:33:07 -04:00
const int c = pStyle -> m_iColumnDrawOrder [ j ];
2014-12-26 01:54:34 -07:00
m_ColumnRenderers [ c ]. Draw ();
2011-03-17 01:47:30 -04:00
}
cur -> m_GhostArrowRow . Draw ();
}
2015-04-03 14:19:28 -06:00
void NoteField :: DrawBoardPrimitive ()
{
if ( ! SHOW_BOARD )
{
return ;
}
m_drawing_board_primitive = true ;
Draw ();
m_drawing_board_primitive = false ;
}
2011-03-17 01:47:30 -04:00
void NoteField :: FadeToFail ()
{
2022-07-10 18:28:56 +03:00
m_FieldRenderArgs . fail_fade = std :: max ( 0.0f , m_FieldRenderArgs . fail_fade ); // this will slowly increase every Update()
2011-03-17 01:47:30 -04:00
// don't fade all over again if this is called twice
}
2014-11-30 23:17:12 -07:00
// A few functions and macros to take care of processing the callback
// return values, since the code would be identical in all of them. -Kyz
#define OPEN_CALLBACK_BLOCK(member_name) \
if(!from_lua && !member_name.IsNil()) \
{ \
Lua* L= LUA->Get(); \
member_name.PushSelf(L);
#define OPEN_RUN_BLOCK(arg_count) \
2017-04-04 17:21:55 -07:00
RString error= "Error running callback: "; \
if(LuaHelpers::RunScriptOnStack(L, error, arg_count, arg_count, true)) \
2014-11-30 23:17:12 -07:00
{
#define CLOSE_RUN_AND_CALLBACK_BLOCKS } lua_settop(L, 0); LUA->Release(L); }
2014-12-18 02:36:45 -07:00
#define PUSH_COLUMN lua_pushnumber(L, col+1)
2014-11-30 23:17:12 -07:00
2014-12-10 18:14:00 -07:00
static void get_returned_column ( Lua * L , PlayerNumber pn , int index , int & col )
2014-11-30 23:17:12 -07:00
{
if ( lua_isnumber ( L , index ))
{
// 1-indexed columns in lua
int tmpcol = lua_tonumber ( L , index ) - 1 ;
2014-12-10 18:14:00 -07:00
if ( tmpcol < 0 || tmpcol >= GAMESTATE -> GetCurrentStyle ( pn ) -> m_iColsPerPlayer )
2014-11-30 23:17:12 -07:00
{
LuaHelpers :: ReportScriptErrorFmt (
"Column returned by callback must be between 1 and %d "
"(GAMESTATE:GetCurrentStyle():ColumnsPerPlayer())." ,
2014-12-10 18:14:00 -07:00
GAMESTATE -> GetCurrentStyle ( pn ) -> m_iColsPerPlayer );
2014-11-30 23:17:12 -07:00
}
else
{
col = tmpcol ;
}
}
}
// Templated so it can be used for TNS and HNS. -Kyz
template < class T > static void get_returned_score ( Lua * L , int index , T & score )
{
T maybe_score = Enum :: Check < T > ( L , index , true , true );
if ( maybe_score != EnumTraits < T >:: Invalid )
{
score = maybe_score ;
}
}
static void get_returned_bright ( Lua * L , int index , bool & bright )
{
if ( lua_isboolean ( L , index ))
{
bright = lua_toboolean ( L , index );
}
}
void NoteField :: Step ( int col , TapNoteScore score , bool from_lua )
{
OPEN_CALLBACK_BLOCK ( m_StepCallback );
2014-12-18 02:36:45 -07:00
PUSH_COLUMN ;
2014-11-30 23:17:12 -07:00
Enum :: Push ( L , score );
OPEN_RUN_BLOCK ( 2 );
2014-12-10 18:14:00 -07:00
get_returned_column ( L , m_pPlayerState -> m_PlayerNumber , 1 , col );
2014-11-30 23:17:12 -07:00
get_returned_score ( L , 2 , score );
CLOSE_RUN_AND_CALLBACK_BLOCKS ;
m_pCurDisplay -> m_ReceptorArrowRow . Step ( col , score );
}
void NoteField :: SetPressed ( int col , bool from_lua )
{
OPEN_CALLBACK_BLOCK ( m_SetPressedCallback );
2014-12-18 02:36:45 -07:00
PUSH_COLUMN ;
2014-11-30 23:17:12 -07:00
OPEN_RUN_BLOCK ( 1 );
2014-12-10 18:14:00 -07:00
get_returned_column ( L , m_pPlayerState -> m_PlayerNumber , 1 , col );
2014-11-30 23:17:12 -07:00
CLOSE_RUN_AND_CALLBACK_BLOCKS ;
m_pCurDisplay -> m_ReceptorArrowRow . SetPressed ( col );
}
void NoteField :: DidTapNote ( int col , TapNoteScore score , bool bright , bool from_lua )
{
OPEN_CALLBACK_BLOCK ( m_DidTapNoteCallback );
2014-12-18 02:36:45 -07:00
PUSH_COLUMN ;
2014-11-30 23:17:12 -07:00
Enum :: Push ( L , score );
lua_pushboolean ( L , bright );
OPEN_RUN_BLOCK ( 3 );
2014-12-10 18:14:00 -07:00
get_returned_column ( L , m_pPlayerState -> m_PlayerNumber , 1 , col );
2014-11-30 23:17:12 -07:00
get_returned_score ( L , 2 , score );
get_returned_bright ( L , 3 , bright );
CLOSE_RUN_AND_CALLBACK_BLOCKS ;
m_pCurDisplay -> m_GhostArrowRow . DidTapNote ( col , score , bright );
}
void NoteField :: DidHoldNote ( int col , HoldNoteScore score , bool bright , bool from_lua )
{
OPEN_CALLBACK_BLOCK ( m_DidHoldNoteCallback );
2014-12-18 02:36:45 -07:00
PUSH_COLUMN ;
2014-11-30 23:17:12 -07:00
Enum :: Push ( L , score );
lua_pushboolean ( L , bright );
OPEN_RUN_BLOCK ( 3 );
2014-12-10 18:14:00 -07:00
get_returned_column ( L , m_pPlayerState -> m_PlayerNumber , 1 , col );
2014-11-30 23:17:12 -07:00
get_returned_score ( L , 2 , score );
get_returned_bright ( L , 3 , bright );
CLOSE_RUN_AND_CALLBACK_BLOCKS ;
m_pCurDisplay -> m_GhostArrowRow . DidHoldNote ( col , score , bright );
}
#undef OPEN_CALLBACK_BLOCK
#undef OPEN_RUN_BLOCK
#undef CLOSE_RUN_AND_CALLBACK_BLOCKS
2014-12-18 02:36:45 -07:00
#undef PUSH_COLUMN
2011-03-17 01:47:30 -04:00
void NoteField :: HandleMessage ( const Message & msg )
{
if ( msg == Message_CurrentSongChanged )
{
m_fCurrentBeatLastUpdate = - 1 ;
m_fYPosCurrentBeatLastUpdate = - 1 ;
}
ActorFrame :: HandleMessage ( msg );
}
2014-11-30 23:17:12 -07:00
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to the Notefield. */
class LunaNoteField : public Luna < NoteField >
{
public :
#define SET_CALLBACK_GENERIC(callback_name, member_name) \
static int callback_name(T* p, lua_State* L) \
{ \
if(lua_isnoneornil(L, 1)) \
{ \
p->member_name.SetFromNil(); \
} \
else if(lua_isfunction(L, 1)) \
{ \
p->member_name.SetFromStack(L); \
} \
else \
{ \
luaL_error(L, #callback_name "Callback argument must be nil (to clear the callback) or a function (to set the callback)."); \
} \
return 0; \
}
2015-01-06 20:14:59 -07:00
SET_CALLBACK_GENERIC ( set_step_callback , m_StepCallback );
SET_CALLBACK_GENERIC ( set_set_pressed_callback , m_SetPressedCallback );
SET_CALLBACK_GENERIC ( set_did_tap_note_callback , m_DidTapNoteCallback );
SET_CALLBACK_GENERIC ( set_did_hold_note_callback , m_DidHoldNoteCallback );
2014-11-30 23:17:12 -07:00
#undef SET_CALLBACK_GENERIC
2014-12-10 18:14:00 -07:00
static int check_column ( lua_State * L , int index , PlayerNumber pn )
2014-11-30 23:17:12 -07:00
{
// 1-indexed columns in lua
int col = IArg ( 1 ) - 1 ;
2014-12-10 18:14:00 -07:00
if ( col < 0 || col >= GAMESTATE -> GetCurrentStyle ( pn ) -> m_iColsPerPlayer )
2014-11-30 23:17:12 -07:00
{
luaL_error ( L , "Column must be between 1 and %d "
2014-12-10 18:14:00 -07:00
"(GAMESTATE:GetCurrentStyle(pn):ColumnsPerPlayer())." ,
GAMESTATE -> GetCurrentStyle ( pn ) -> m_iColsPerPlayer );
2014-11-30 23:17:12 -07:00
}
return col ;
}
2015-01-06 20:14:59 -07:00
static int step ( T * p , lua_State * L )
2014-11-30 23:17:12 -07:00
{
2014-12-10 18:14:00 -07:00
int col = check_column ( L , 1 , p -> GetPlayerState () -> m_PlayerNumber );
2014-11-30 23:17:12 -07:00
TapNoteScore tns = Enum :: Check < TapNoteScore > ( L , 2 );
p -> Step ( col , tns , true );
return 0 ;
}
2015-01-06 20:14:59 -07:00
static int set_pressed ( T * p , lua_State * L )
2014-11-30 23:17:12 -07:00
{
2014-12-10 18:14:00 -07:00
int col = check_column ( L , 1 , p -> GetPlayerState () -> m_PlayerNumber );
2014-11-30 23:17:12 -07:00
p -> SetPressed ( col , true );
return 0 ;
}
2015-01-06 20:14:59 -07:00
static int did_tap_note ( T * p , lua_State * L )
2014-11-30 23:17:12 -07:00
{
2014-12-10 18:14:00 -07:00
int col = check_column ( L , 1 , p -> GetPlayerState () -> m_PlayerNumber );
2014-11-30 23:17:12 -07:00
TapNoteScore tns = Enum :: Check < TapNoteScore > ( L , 2 );
bool bright = BArg ( 3 );
p -> DidTapNote ( col , tns , bright , true );
return 0 ;
}
2015-01-06 20:14:59 -07:00
static int did_hold_note ( T * p , lua_State * L )
2014-11-30 23:17:12 -07:00
{
2014-12-10 18:14:00 -07:00
int col = check_column ( L , 1 , p -> GetPlayerState () -> m_PlayerNumber );
2014-11-30 23:17:12 -07:00
HoldNoteScore hns = Enum :: Check < HoldNoteScore > ( L , 2 );
bool bright = BArg ( 3 );
p -> DidHoldNote ( col , hns , bright , true );
return 0 ;
}
2015-01-01 04:36:20 -07:00
static int get_column_actors ( T * p , lua_State * L )
2014-12-26 01:54:34 -07:00
{
lua_createtable ( L , p -> m_ColumnRenderers . size (), 0 );
2023-04-19 23:04:25 +02:00
for ( std :: size_t i = 0 ; i < p -> m_ColumnRenderers . size (); ++ i )
2014-12-26 01:54:34 -07:00
{
p -> m_ColumnRenderers [ i ]. PushSelf ( L );
lua_rawseti ( L , - 2 , i + 1 );
}
return 1 ;
}
2024-02-15 14:37:56 -06:00
static int GetBeatBars ( T * p , lua_State * L )
{
LuaHelpers :: Push ( L , p -> GetBeatBars ());
return 1 ;
};
static int SetBeatBars ( T * p , lua_State * L )
{
p -> SetBeatBars ( BArg ( 1 ));
return 0 ;
}
2024-03-03 00:08:55 -08:00
static int SetBeatBarsAlpha ( T * p , lua_State * L )
{
p -> SetBeatBarsAlpha ( FArg ( 1 ), FArg ( 2 ), FArg ( 3 ), FArg ( 4 ));
return 0 ;
}
2014-11-30 23:17:12 -07:00
LunaNoteField ()
{
2015-01-06 20:14:59 -07:00
ADD_METHOD ( set_step_callback );
ADD_METHOD ( set_set_pressed_callback );
ADD_METHOD ( set_did_tap_note_callback );
ADD_METHOD ( set_did_hold_note_callback );
ADD_METHOD ( step );
ADD_METHOD ( set_pressed );
ADD_METHOD ( did_tap_note );
ADD_METHOD ( did_hold_note );
2015-01-01 04:36:20 -07:00
ADD_METHOD ( get_column_actors );
2024-02-15 14:37:56 -06:00
ADD_METHOD ( GetBeatBars );
ADD_METHOD ( SetBeatBars );
2024-03-03 00:08:55 -08:00
ADD_METHOD ( SetBeatBarsAlpha );
2014-11-30 23:17:12 -07:00
}
};
LUA_REGISTER_DERIVED_CLASS ( NoteField , ActorFrame )
// lua end
2011-03-17 01:47:30 -04:00
/*
* (c) 2001-2004 Chris Danford
* All rights reserved.
2023-04-19 14:22:59 +02:00
*
2011-03-17 01:47:30 -04:00
* 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.
2023-04-19 14:22:59 +02:00
*
2011-03-17 01:47:30 -04:00
* 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.
*/