Merge pull request #10 from teejusb/tns

Add support for disabling timing windows on a per-player basis
This commit is contained in:
teejusb
2022-01-05 18:21:57 -08:00
committed by GitHub
8 changed files with 204 additions and 54 deletions
+4
View File
@@ -1248,6 +1248,7 @@
<Function name='DigitalZSteps'/> <Function name='DigitalZSteps'/>
<Function name='Dizzy'/> <Function name='Dizzy'/>
<Function name='DizzyHolds'/> <Function name='DizzyHolds'/>
<Function name='DisableTimingWindow'/>
<Function name='Distant'/> <Function name='Distant'/>
<Function name='DrainSetting'/> <Function name='DrainSetting'/>
<Function name='DrawSize'/> <Function name='DrawSize'/>
@@ -1267,6 +1268,7 @@
<Function name='Flip'/> <Function name='Flip'/>
<Function name='Floored'/> <Function name='Floored'/>
<Function name='FromString'/> <Function name='FromString'/>
<Function name='GetDisabledTimingWindows'/>
<Function name='GetReversePercentForColumn'/> <Function name='GetReversePercentForColumn'/>
<Function name='GetStepAttacks'/> <Function name='GetStepAttacks'/>
<Function name='IsEasierForSongAndSteps'/> <Function name='IsEasierForSongAndSteps'/>
@@ -1319,6 +1321,7 @@
<Function name='RandAttack'/> <Function name='RandAttack'/>
<Function name='RandomSpeed'/> <Function name='RandomSpeed'/>
<Function name='RandomVanish'/> <Function name='RandomVanish'/>
<Function name='ResetDisabledTimingWindows'/>
<Function name='Reverse'/> <Function name='Reverse'/>
<Function name='Reversen'/> <Function name='Reversen'/>
<Function name='Right'/> <Function name='Right'/>
@@ -1368,6 +1371,7 @@
<Function name='Twirl'/> <Function name='Twirl'/>
<Function name='Twister'/> <Function name='Twister'/>
<Function name='UsingReverse'/> <Function name='UsingReverse'/>
<Function name='VisualDelay'/>
<Function name='Wave'/> <Function name='Wave'/>
<Function name='WavePeriod'/> <Function name='WavePeriod'/>
<Function name='Wide'/> <Function name='Wide'/>
+15
View File
@@ -3915,6 +3915,10 @@ a,b = options:Boost(5)
</Function> </Function>
<Function name='Dizzy' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='Dizzy' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='DizzyHolds' return='bool' arguments='bool value'> </Function> <Function name='DizzyHolds' return='bool' arguments='bool value'> </Function>
<Function name='DisableTimingWindow' return='' arguments='TimingWindow tw'>
Selectively disable specific timing windows for a player. <br />
Valid values are <code>W1</code> to <code>W5</code> as defined in the <Link class='ENUM' function='TimingWindow' /> enum.
</Function>
<Function name='Distant' return='float, float' arguments='float value, float approach_speed'> <Function name='Distant' return='float, float' arguments='float value, float approach_speed'>
If the player is using Distant (zero skew and positive tilt), returns the value of tilt and its approach_speed.<br /> If the player is using Distant (zero skew and positive tilt), returns the value of tilt and its approach_speed.<br />
Returns nil otherwise.<br /> Returns nil otherwise.<br />
@@ -3960,6 +3964,9 @@ a,b = options:Boost(5)
</Function> </Function>
<Function name='Flip' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='Flip' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='Floored' return='bool' arguments='bool value'> </Function> <Function name='Floored' return='bool' arguments='bool value'> </Function>
<Function name='GetDisabledTimingWindows' return='{TimingWindow}' arguments=''>
Returns a table of the currently disabled <Link class='ENUM' function='TimingWindow' />s for the player.
</Function>
<Function name='GetStepAttacks' return='bool' arguments=''> <Function name='GetStepAttacks' return='bool' arguments=''>
Returns true if step attacks or random attacks are enabled. Returns true if step attacks or random attacks are enabled.
</Function> </Function>
@@ -4065,6 +4072,9 @@ prev_note_name, succeeded = options:NoteSkin("cel")
<Function name='RandAttack' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='RandAttack' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='RandomSpeed' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='RandomSpeed' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='RandomVanish' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='RandomVanish' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='ResetDisabledTimingWindows' return='' arguments=''>
Re-enable all <Link class='ENUM' function='TimingWindow' />s that may have previously been disabled.
</Function>
<Function name='Reverse' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='Reverse' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='Reversen' return= 'float, float' arguments='float value, float approach_speed'> <Function name='Reversen' return= 'float, float' arguments='float value, float approach_speed'>
Use 1-16 in place of 'n' to apply Reverse on a specific column. Use 1-16 in place of 'n' to apply Reverse on a specific column.
@@ -4165,6 +4175,11 @@ prev_note_name, succeeded = options:NoteSkin("cel")
<Function name='UsingReverse' return='bool' arguments=''> <Function name='UsingReverse' return='bool' arguments=''>
Returns <code>true</code> if the player is using reverse. (equivalent to <code>GetReverse() == 1.0</code>) Returns <code>true</code> if the player is using reverse. (equivalent to <code>GetReverse() == 1.0</code>)
</Function> </Function>
<Function name='VisualDelay' return='' arguments='float'>
The time in seconds to adjust a player's visual delay by.<br />
Negative values will shift the arrows up, while positive values will push them down.<br />
Sub-millisecond visual delay values are not saved and are instead rounded to the closest millisecond.
</Function>
<Function name='Wave' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='Wave' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='WavePeriod' return='float, float' arguments='float value, float approach_speed'> </Function> <Function name='WavePeriod' return='float, float' arguments='float value, float approach_speed'> </Function>
<Function name='Wide' return='bool' arguments='bool value'> </Function> <Function name='Wide' return='bool' arguments='bool value'> </Function>
+2
View File
@@ -283,6 +283,8 @@ static const char *TimingWindowNames[] = {
"Checkpoint" "Checkpoint"
}; };
XToString( TimingWindow ); XToString( TimingWindow );
LuaXType( TimingWindow );
StringToX( TimingWindow );
static const char *ScoreEventNames[] = { static const char *ScoreEventNames[] = {
"CheckpointHit", "CheckpointHit",
+3 -1
View File
@@ -294,9 +294,11 @@ enum TimingWindow
TW_Hold, TW_Hold,
TW_Roll, TW_Roll,
TW_Checkpoint, TW_Checkpoint,
NUM_TimingWindow NUM_TimingWindow,
TimingWindow_Invalid,
}; };
const RString& TimingWindowToString( TimingWindow tw ); const RString& TimingWindowToString( TimingWindow tw );
LuaDeclareType( TimingWindow );
/** @brief The list of score events that can take place while playing. */ /** @brief The list of score events that can take place while playing. */
enum ScoreEvent enum ScoreEvent
+45 -24
View File
@@ -104,7 +104,7 @@ static const float StepSearchDistance = 1.0f;
void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ) void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut )
{ {
sNameOut = "TimingWindowSeconds" + TimingWindowToString( (TimingWindow)i ); sNameOut = "TimingWindowSeconds" + TimingWindowToString( static_cast<TimingWindow>(i) );
switch( i ) switch( i )
{ {
case TW_W1: case TW_W1:
@@ -1002,11 +1002,17 @@ void Player::Update( float fDeltaTime )
// were held at some point before getting judged. // were held at some point before getting judged.
{ {
float largestWindow = 0.0f; float largestWindow = 0.0f;
largestWindow = max(largestWindow, GetWindowSeconds(TW_W1)); const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows;
largestWindow = max(largestWindow, GetWindowSeconds(TW_W2)); if (!disabledWindows[TW_W1])
largestWindow = max(largestWindow, GetWindowSeconds(TW_W3)); largestWindow = max(largestWindow, GetWindowSeconds(TW_W1));
largestWindow = max(largestWindow, GetWindowSeconds(TW_W4)); if (!disabledWindows[TW_W2])
largestWindow = max(largestWindow, GetWindowSeconds(TW_W5)); largestWindow = max(largestWindow, GetWindowSeconds(TW_W2));
if (!disabledWindows[TW_W3])
largestWindow = max(largestWindow, GetWindowSeconds(TW_W3));
if (!disabledWindows[TW_W4])
largestWindow = max(largestWindow, GetWindowSeconds(TW_W4));
if (!disabledWindows[TW_W5])
largestWindow = max(largestWindow, GetWindowSeconds(TW_W5));
// We have to check the unjudged notes that are within the // We have to check the unjudged notes that are within the
// timing window. Let's find the cutoff point! (lastCheckRow) // timing window. Let's find the cutoff point! (lastCheckRow)
@@ -2264,18 +2270,31 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
// bug. (It was fNoteOffset > 0.f before) -DaisuMaster // bug. (It was fNoteOffset > 0.f before) -DaisuMaster
if( !REQUIRE_STEP_ON_HOLD_HEADS && ( fNoteOffset <= GetWindowSeconds( TW_W5 ) && GetWindowSeconds( TW_W5 ) != 0 ) ) if( !REQUIRE_STEP_ON_HOLD_HEADS && ( fNoteOffset <= GetWindowSeconds( TW_W5 ) && GetWindowSeconds( TW_W5 ) != 0 ) )
{ {
score = TNS_W1; // Set it to the first non-disabled window.
const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows;
if (!disabledWindows[TW_W1])
score = TNS_W1;
else if (!disabledWindows[TW_W2])
score = TNS_W2;
else if (!disabledWindows[TW_W3])
score = TNS_W3;
else if (!disabledWindows[TW_W4])
score = TNS_W4;
else if (!disabledWindows[TW_W5])
score = TNS_W5;
break; break;
} }
// Fall through to default. // Fall through to default.
default: default:
if( (pTN->type == TapNoteType_Lift) == bRelease ) if( (pTN->type == TapNoteType_Lift) == bRelease )
{ {
if( fSecondsFromExact <= GetWindowSeconds(TW_W1) ) score = TNS_W1; const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows;
else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) ) score = TNS_W2; if( fSecondsFromExact <= GetWindowSeconds(TW_W1) && !disabledWindows[TW_W1] ) score = TNS_W1;
else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) ) score = TNS_W3; else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) && !disabledWindows[TW_W2] ) score = TNS_W2;
else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) ) score = TNS_W4; else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) && !disabledWindows[TW_W3] ) score = TNS_W3;
else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) ) score = TNS_W5; else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) && !disabledWindows[TW_W4] ) score = TNS_W4;
else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) && !disabledWindows[TW_W5] ) score = TNS_W5;
} }
break; break;
} }
@@ -2283,7 +2302,8 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
case PC_CPU: case PC_CPU:
case PC_AUTOPLAY: case PC_AUTOPLAY:
score = PlayerAI::GetTapNoteScore( m_pPlayerState ); {
score = PlayerAI::GetTapNoteScore(m_pPlayerState);
/* XXX: This doesn't make sense. /* XXX: This doesn't make sense.
* Step should only be called in autoplay for hit notes. */ * Step should only be called in autoplay for hit notes. */
@@ -2300,14 +2320,14 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
// TRICKY: We're asking the AI to judge mines. Consider TNS_W4 and // TRICKY: We're asking the AI to judge mines. Consider TNS_W4 and
// below as "mine was hit" and everything else as "mine was avoided" // below as "mine was hit" and everything else as "mine was avoided"
if( pTN->type == TapNoteType_Mine ) if ( pTN->type == TapNoteType_Mine )
{ {
// The CPU hits a lot of mines. Only consider hitting the // The CPU hits a lot of mines. Only consider hitting the
// first mine for a row. We know we're the first mine if // first mine for a row. We know we're the first mine if
// there are are no mines to the left of us. // there are are no mines to the left of us.
for( int t=0; t<col; t++ ) for ( int t=0; t<col; t++ )
{ {
if( m_NoteData.GetTapNote(t,iRowOfOverlappingNoteOrRow).type == TapNoteType_Mine ) // there's a mine to the left of us if( m_NoteData.GetTapNote(t, iRowOfOverlappingNoteOrRow).type == TapNoteType_Mine ) // there's a mine to the left of us
return; // avoid return; // avoid
} }
@@ -2316,20 +2336,20 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
bool bTapsOnRow = m_NoteData.IsThereATapOrHoldHeadAtRow( iRowOfOverlappingNoteOrRow ); bool bTapsOnRow = m_NoteData.IsThereATapOrHoldHeadAtRow( iRowOfOverlappingNoteOrRow );
TapNoteScore get_to_avoid = bTapsOnRow ? TNS_W3 : TNS_W4; TapNoteScore get_to_avoid = bTapsOnRow ? TNS_W3 : TNS_W4;
if( score >= get_to_avoid ) if (score >= get_to_avoid )
return; // avoided return; // avoided
else else
score = TNS_HitMine; score = TNS_HitMine;
} }
if( pTN->type == TapNoteType_Attack && score > TNS_W4 ) if ( pTN->type == TapNoteType_Attack && score > TNS_W4 )
score = TNS_W2; // sentinel score = TNS_W2; // sentinel
/* AI will generate misses here. Don't handle a miss like a regular /* AI will generate misses here. Don't handle a miss like a regular
* note because we want the judgment animation to appear delayed. * note because we want the judgment animation to appear delayed.
* Instead, return early if AI generated a miss, and let * Instead, return early if AI generated a miss, and let
* UpdateTapNotesMissedOlderThan() detect and handle the misses. */ * UpdateTapNotesMissedOlderThan() detect and handle the misses. */
if( score == TNS_Miss ) if ( score == TNS_Miss )
return; return;
// Put some small, random amount in fNoteOffset so that demonstration // Put some small, random amount in fNoteOffset so that demonstration
@@ -2347,7 +2367,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
float fWindowW5 = GetWindowSeconds(TW_W5); float fWindowW5 = GetWindowSeconds(TW_W5);
// W1 is the top judgment, there is no overlap. // W1 is the top judgment, there is no overlap.
if( score == TNS_W1 ) if ( score == TNS_W1 )
fNoteOffset = randomf(-fWindowW1, fWindowW1); fNoteOffset = randomf(-fWindowW1, fWindowW1);
else else
{ {
@@ -2355,25 +2375,25 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
float fLowerBound = 0.0f; // negative upper limit float fLowerBound = 0.0f; // negative upper limit
float fUpperBound = 0.0f; // positive lower limit float fUpperBound = 0.0f; // positive lower limit
float fCompareWindow = 0.0f; // filled in here: float fCompareWindow = 0.0f; // filled in here:
if( score == TNS_W2 ) if ( score == TNS_W2 )
{ {
fLowerBound = -fWindowW1; fLowerBound = -fWindowW1;
fUpperBound = fWindowW1; fUpperBound = fWindowW1;
fCompareWindow = fWindowW2; fCompareWindow = fWindowW2;
} }
else if( score == TNS_W3 ) else if ( score == TNS_W3 )
{ {
fLowerBound = -fWindowW2; fLowerBound = -fWindowW2;
fUpperBound = fWindowW2; fUpperBound = fWindowW2;
fCompareWindow = fWindowW3; fCompareWindow = fWindowW3;
} }
else if( score == TNS_W4 ) else if ( score == TNS_W4 )
{ {
fLowerBound = -fWindowW3; fLowerBound = -fWindowW3;
fUpperBound = fWindowW3; fUpperBound = fWindowW3;
fCompareWindow = fWindowW4; fCompareWindow = fWindowW4;
} }
else if( score == TNS_W5 ) else if ( score == TNS_W5 )
{ {
fLowerBound = -fWindowW4; fLowerBound = -fWindowW4;
fUpperBound = fWindowW4; fUpperBound = fWindowW4;
@@ -2399,6 +2419,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
fNoteOffset = TapNoteOffset attribute fNoteOffset = TapNoteOffset attribute
break; break;
*/ */
}
default: default:
FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController)); FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController));
} }
+15 -1
View File
@@ -141,7 +141,21 @@ TapNoteScore PlayerAI::GetTapNoteScore( const PlayerState* pPlayerState )
TapScoreDistribution& distribution = g_Distributions[iCpuSkill]; TapScoreDistribution& distribution = g_Distributions[iCpuSkill];
return distribution.GetTapNoteScore(); TapNoteScore score = distribution.GetTapNoteScore();
const auto& disabledWindows = pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows;
// Downgrade the TapNoteScore if that specific window is disabled.
if (score == TNS_W1 && disabledWindows[TW_W1])
score = TNS_W2;
if (score == TNS_W2 && disabledWindows[TW_W2])
score = TNS_W3;
if (score == TNS_W3 && disabledWindows[TW_W3])
score = TNS_W4;
if (score == TNS_W4 && disabledWindows[TW_W4])
score = TNS_W5;
if (score == TNS_W5 && disabledWindows[TW_W5])
score = TNS_None;
return score;
} }
/* /*
+113 -28
View File
@@ -10,6 +10,7 @@
#include "Style.h" #include "Style.h"
#include "CommonMetrics.h" #include "CommonMetrics.h"
#include <float.h> #include <float.h>
#include <sstream>
static const char *LifeTypeNames[] = { static const char *LifeTypeNames[] = {
"Bar", "Bar",
@@ -96,6 +97,7 @@ void PlayerOptions::Init()
m_bCosecant = false; m_bCosecant = false;
m_sNoteSkin = ""; m_sNoteSkin = "";
m_fVisualDelay = 0.0f; m_fVisualDelay = 0.0f;
m_twDisabledWindows.reset();
ZERO( m_fMovesX ); ONE( m_SpeedfMovesX ); ZERO( m_fMovesX ); ONE( m_SpeedfMovesX );
ZERO( m_fMovesY ); ONE( m_SpeedfMovesY ); ZERO( m_fMovesY ); ONE( m_SpeedfMovesY );
ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ ); ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ );
@@ -187,6 +189,7 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
DO_COPY( m_MinTNSToHideNotes ); DO_COPY( m_MinTNSToHideNotes );
DO_COPY( m_sNoteSkin ); DO_COPY( m_sNoteSkin );
DO_COPY( m_fVisualDelay ); DO_COPY( m_fVisualDelay );
DO_COPY( m_twDisabledWindows );
#undef APPROACH #undef APPROACH
#undef DO_COPY #undef DO_COPY
} }
@@ -564,6 +567,25 @@ void PlayerOptions::GetMods( vector<RString> &AddTo, bool bForceNoteSkin ) const
// Note that we don't process sub-millisecond visual delay. // Note that we don't process sub-millisecond visual delay.
AddTo.push_back( ssprintf("%.0fms VisualDelay", m_fVisualDelay * 1000.0f) ); AddTo.push_back( ssprintf("%.0fms VisualDelay", m_fVisualDelay * 1000.0f) );
} }
if (m_twDisabledWindows.count() != 0) {
std::stringstream ss;
bool is_first = true;
ss << "No ";
for (int i=TW_W1; i != TW_W5; ++i) {
if (m_twDisabledWindows[i]) {
if (!is_first) {
ss << "/";
} else {
is_first = true;
}
ss << TimingWindowToString(static_cast<TimingWindow>(i)).c_str();
}
}
// Final string will be something like "No W4/W5"
AddTo.push_back(ss.str());
}
} }
/* Options are added to the current settings; call Init() beforehand if /* Options are added to the current settings; call Init() beforehand if
@@ -644,6 +666,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
const bool on = (level > 0.5f); const bool on = (level > 0.5f);
static Regex mult("^([0-9]+(\\.[0-9]+)?)x$"); static Regex mult("^([0-9]+(\\.[0-9]+)?)x$");
static Regex disabledWindows("(w[1-5])");
vector<RString> matches; vector<RString> matches;
if( mult.Compare(sBit, matches) ) if( mult.Compare(sBit, matches) )
{ {
@@ -1104,44 +1127,64 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
{ {
if (sBit.find("x") != sBit.npos) if (sBit.find("x") != sBit.npos)
{ {
for (int i=0; i<16; i++) for (int i=0; i<16; i++)
{ {
sMod = ssprintf( "movex%d", i+1 ); sMod = ssprintf( "movex%d", i+1 );
if( sBit == sMod) if( sBit == sMod)
{ {
SET_FLOAT( fMovesX[i] ) SET_FLOAT( fMovesX[i] )
break; break;
} }
} }
} }
else if (sBit.find("y") != sBit.npos) else if (sBit.find("y") != sBit.npos)
{ {
for (int i=0; i<16; i++) for (int i=0; i<16; i++)
{ {
sMod = ssprintf( "movey%d", i+1 ); sMod = ssprintf( "movey%d", i+1 );
if( sBit == sMod) if( sBit == sMod)
{ {
SET_FLOAT( fMovesY[i] ) SET_FLOAT( fMovesY[i] )
break; break;
} }
} }
} }
else if (sBit.find("z") != sBit.npos) else if (sBit.find("z") != sBit.npos)
{ {
for (int i=0; i<16; i++) for (int i=0; i<16; i++)
{ {
sMod = ssprintf( "movez%d", i+1 ); sMod = ssprintf( "movez%d", i+1 );
if( sBit == sMod) if( sBit == sMod)
{ {
SET_FLOAT( fMovesZ[i] ) SET_FLOAT( fMovesZ[i] )
break; break;
} }
} }
} }
} }
else if( sBit == "zbuffer" ) m_bZBuffer = on; else if( sBit == "zbuffer" ) m_bZBuffer = on;
else if( sBit == "cosecant" ) m_bCosecant = on; else if( sBit == "cosecant" ) m_bCosecant = on;
else if( sBit == "visualdelay" ) m_fVisualDelay = level; else if( sBit == "visualdelay" ) m_fVisualDelay = level;
else if( level == 0 && disabledWindows.Compare(sBit)) // "No w1" etc.
{
// We come into this condition if there is at least a single window present but there may be more.
// To get all of the windows, we go through in a loop to extract all of them.
static Regex allDisabledWindows("(w[1-5])(.*)$");
RString input = sBit;
while (true)
{
if (!allDisabledWindows.Compare(input, matches))
break;
TimingWindow tw;
bool ret = StringConversion::FromString(matches[0].MakeUpper(), tw);
if (ret && TW_W1 <= tw && tw <= TW_W5)
{
m_twDisabledWindows.set(tw);
}
input = matches[1];
}
}
// deprecated mods/left in for compatibility // deprecated mods/left in for compatibility
else if( sBit == "converge" ) SET_FLOAT( fScrolls[SCROLL_CENTERED] ) else if( sBit == "converge" ) SET_FLOAT( fScrolls[SCROLL_CENTERED] )
// end of the list // end of the list
@@ -1395,6 +1438,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
return false; return false;
} }
COMPARE(m_fVisualDelay); COMPARE(m_fVisualDelay);
COMPARE(m_twDisabledWindows); // != is defined correctly for ordered sets.
for( int i = 0; i < PlayerOptions::NUM_ACCELS; ++i ) for( int i = 0; i < PlayerOptions::NUM_ACCELS; ++i )
COMPARE(m_fAccels[i]); COMPARE(m_fAccels[i]);
for( int i = 0; i < PlayerOptions::NUM_EFFECTS; ++i ) for( int i = 0; i < PlayerOptions::NUM_EFFECTS; ++i )
@@ -1461,6 +1505,7 @@ PlayerOptions& PlayerOptions::operator=(PlayerOptions const& other)
CPY(m_bZBuffer); CPY(m_bZBuffer);
CPY(m_bCosecant); CPY(m_bCosecant);
CPY(m_fVisualDelay); CPY(m_fVisualDelay);
CPY(m_twDisabledWindows);
CPY_SPEED(fDark); CPY_SPEED(fDark);
CPY_SPEED(fBlind); CPY_SPEED(fBlind);
CPY_SPEED(fCover); CPY_SPEED(fCover);
@@ -1693,6 +1738,7 @@ RString PlayerOptions::GetSavedPrefsString() const
SAVE( m_bMuteOnError ); SAVE( m_bMuteOnError );
SAVE( m_sNoteSkin ); SAVE( m_sNoteSkin );
SAVE( m_fVisualDelay ); SAVE( m_fVisualDelay );
SAVE( m_twDisabledWindows );
#undef SAVE #undef SAVE
return po_prefs.GetString(); return po_prefs.GetString();
} }
@@ -1741,6 +1787,7 @@ void PlayerOptions::ResetPrefs( ResetPrefsType type )
// Don't clear this. // Don't clear this.
// CPY( m_sNoteSkin ); // CPY( m_sNoteSkin );
CPY(m_fVisualDelay); CPY(m_fVisualDelay);
CPY(m_twDisabledWindows);
#undef CPY #undef CPY
} }
@@ -1991,6 +2038,41 @@ public:
FLOAT_NO_SPEED_INTERFACE(VisualDelay, VisualDelay, true); FLOAT_NO_SPEED_INTERFACE(VisualDelay, VisualDelay, true);
static int DisableTimingWindow(T* p, lua_State* L)
{
int original_top= lua_gettop(L);
if (original_top >= 1 && !lua_isnil(L, 1))
{
// Insert the specified TimingWindow into the disabled windows set.
p->m_twDisabledWindows.set(Enum::Check<TimingWindow>(L, 1));
}
OPTIONAL_RETURN_SELF(original_top);
return 1;
}
static int ResetDisabledTimingWindows(T* p, lua_State* L)
{
int original_top= lua_gettop(L);
p->m_twDisabledWindows.reset();
OPTIONAL_RETURN_SELF(original_top);
return 1;
}
static int GetDisabledTimingWindows(T* p, lua_State* L)
{
int original_top= lua_gettop(L);
lua_newtable( L );
int j = 0;
for (int i=TW_W1; i != TW_W5; ++i) {
if (p->m_twDisabledWindows[i]) {
Enum::Push(L, static_cast<TimingWindow>(i));
lua_rawseti( L, -2, j+1 );
++j;
}
}
return 1;
}
// NoteSkins // NoteSkins
static int NoteSkin(T* p, lua_State* L) static int NoteSkin(T* p, lua_State* L)
{ {
@@ -2509,12 +2591,15 @@ public:
ADD_MULTICOL_METHOD(Bumpy); ADD_MULTICOL_METHOD(Bumpy);
ADD_MULTICOL_METHOD(Reverse); ADD_MULTICOL_METHOD(Reverse);
ADD_METHOD(NoteSkin); ADD_METHOD(NoteSkin);
ADD_METHOD(FailSetting); ADD_METHOD(FailSetting);
ADD_METHOD(MinTNSToHideNotes); ADD_METHOD(MinTNSToHideNotes);
ADD_METHOD(VisualDelay); ADD_METHOD(VisualDelay);
ADD_METHOD(DisableTimingWindow);
ADD_METHOD(ResetDisabledTimingWindows);
ADD_METHOD(GetDisabledTimingWindows);
// Speed // Speed
ADD_METHOD( CMod ); ADD_METHOD( CMod );
ADD_METHOD( XMod ); ADD_METHOD( XMod );
+7
View File
@@ -9,6 +9,8 @@ struct lua_State;
#define ONE( arr ) { for( unsigned Z = 0; Z < ARRAYLEN(arr); ++Z ) arr[Z]=1.0f; } #define ONE( arr ) { for( unsigned Z = 0; Z < ARRAYLEN(arr); ++Z ) arr[Z]=1.0f; }
#include <bitset>
#include "GameConstantsAndTypes.h" #include "GameConstantsAndTypes.h"
#include "PlayerNumber.h" #include "PlayerNumber.h"
#include "PrefsManager.h" #include "PrefsManager.h"
@@ -398,6 +400,11 @@ public:
/** @brief The Visual Delay additionally applied on a per-player basis in ms. */ /** @brief The Visual Delay additionally applied on a per-player basis in ms. */
float m_fVisualDelay; float m_fVisualDelay;
/** @brief The TimingWindow that can be disabled.
* Valid values are only W1-W5 which map to indices 0-5 respectively.
* Other values are ignored. */
std::bitset<5> m_twDisabledWindows;
void NextAccel(); void NextAccel();
void NextEffect(); void NextEffect();
void NextAppearance(); void NextAppearance();