From c10d8e4eb5a659748e0cfe733a5511bbe0c99b90 Mon Sep 17 00:00:00 2001 From: teejusb <5017202+teejusb@users.noreply.github.com> Date: Fri, 10 Dec 2021 00:27:21 -0800 Subject: [PATCH 1/4] Initial Implementation of disabling timing windows per player. --- src/GameConstantsAndTypes.cpp | 1 + src/GameConstantsAndTypes.h | 4 +- src/Player.cpp | 95 ++++++++++++++++++++++++----------- src/PlayerOptions.cpp | 79 ++++++++++++++++++++++++++++- src/PlayerOptions.h | 8 +++ 5 files changed, 156 insertions(+), 31 deletions(-) diff --git a/src/GameConstantsAndTypes.cpp b/src/GameConstantsAndTypes.cpp index 12417ca98e..2c6e4d1363 100644 --- a/src/GameConstantsAndTypes.cpp +++ b/src/GameConstantsAndTypes.cpp @@ -283,6 +283,7 @@ static const char *TimingWindowNames[] = { "Checkpoint" }; XToString( TimingWindow ); +LuaXType( TimingWindow ); static const char *ScoreEventNames[] = { "CheckpointHit", diff --git a/src/GameConstantsAndTypes.h b/src/GameConstantsAndTypes.h index 1099eb82a5..5caea2055e 100644 --- a/src/GameConstantsAndTypes.h +++ b/src/GameConstantsAndTypes.h @@ -294,9 +294,11 @@ enum TimingWindow TW_Hold, TW_Roll, TW_Checkpoint, - NUM_TimingWindow + NUM_TimingWindow, + TimingWindow_Invalid, }; const RString& TimingWindowToString( TimingWindow tw ); +LuaDeclareType( TimingWindow ); /** @brief The list of score events that can take place while playing. */ enum ScoreEvent diff --git a/src/Player.cpp b/src/Player.cpp index 145d50d3ad..e2ed301697 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1002,11 +1002,17 @@ void Player::Update( float fDeltaTime ) // were held at some point before getting judged. { float largestWindow = 0.0f; - largestWindow = max(largestWindow, GetWindowSeconds(TW_W1)); - largestWindow = max(largestWindow, GetWindowSeconds(TW_W2)); - largestWindow = max(largestWindow, GetWindowSeconds(TW_W3)); - largestWindow = max(largestWindow, GetWindowSeconds(TW_W4)); - largestWindow = max(largestWindow, GetWindowSeconds(TW_W5)); + const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; + if (disabledWindows.find(TW_W1) == disabledWindows.end()) + largestWindow = max(largestWindow, GetWindowSeconds(TW_W1)); + if (disabledWindows.find(TW_W2) == disabledWindows.end()) + largestWindow = max(largestWindow, GetWindowSeconds(TW_W2)); + if (disabledWindows.find(TW_W3) == disabledWindows.end()) + largestWindow = max(largestWindow, GetWindowSeconds(TW_W3)); + if (disabledWindows.find(TW_W4) == disabledWindows.end()) + largestWindow = max(largestWindow, GetWindowSeconds(TW_W4)); + if (disabledWindows.find(TW_W5) == disabledWindows.end()) + largestWindow = max(largestWindow, GetWindowSeconds(TW_W5)); // We have to check the unjudged notes that are within the // 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 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.find(TW_W1) == disabledWindows.end()) + score = TNS_W1; + else if (disabledWindows.find(TW_W2) == disabledWindows.end()) + score = TNS_W2; + else if (disabledWindows.find(TW_W3) == disabledWindows.end()) + score = TNS_W3; + else if (disabledWindows.find(TW_W4) == disabledWindows.end()) + score = TNS_W4; + else if (disabledWindows.find(TW_W5) == disabledWindows.end()) + score = TNS_W5; + break; } // Fall through to default. default: if( (pTN->type == TapNoteType_Lift) == bRelease ) { - if( fSecondsFromExact <= GetWindowSeconds(TW_W1) ) score = TNS_W1; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) ) score = TNS_W2; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) ) score = TNS_W3; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) ) score = TNS_W4; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) ) score = TNS_W5; + const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; + if( fSecondsFromExact <= GetWindowSeconds(TW_W1) && disabledWindows.find(TW_W1) == disabledWindows.end()) score = TNS_W1; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) && disabledWindows.find(TW_W2) == disabledWindows.end()) score = TNS_W2; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) && disabledWindows.find(TW_W3) == disabledWindows.end()) score = TNS_W3; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) && disabledWindows.find(TW_W4) == disabledWindows.end()) score = TNS_W4; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) && disabledWindows.find(TW_W5) == disabledWindows.end()) score = TNS_W5; } break; } @@ -2283,15 +2302,32 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele case PC_CPU: case PC_AUTOPLAY: - score = PlayerAI::GetTapNoteScore( m_pPlayerState ); + { + score = PlayerAI::GetTapNoteScore(m_pPlayerState); + const auto& disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; + for (int i = score; i >= TNS_W5; i--) + { + TapNoteScore cur_score = (TapNoteScore)i; + // Downgrade the TapNoteScore if that specific window is disabled. + if (cur_score == TNS_W1 && disabledWindows.find(TW_W1) == disabledWindows.end()) + score = TNS_W2; + else if (cur_score == TNS_W2 && disabledWindows.find(TW_W2) == disabledWindows.end()) + score = TNS_W3; + else if (cur_score == TNS_W3 && disabledWindows.find(TW_W3) == disabledWindows.end()) + score = TNS_W4; + else if (cur_score == TNS_W4 && disabledWindows.find(TW_W4) == disabledWindows.end()) + score = TNS_W5; + else if (cur_score == TNS_W5 && disabledWindows.find(TW_W5) == disabledWindows.end()) + score = TNS_None; + } /* XXX: This doesn't make sense. * Step should only be called in autoplay for hit notes. */ #if 0 - // GetTapNoteScore always returns TNS_W1 in autoplay. - // If the step is far away, don't judge it. - if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY && - fSecondsFromExact > GetWindowSeconds(TW_W5) ) + // GetTapNoteScore always returns TNS_W1 in autoplay. + // If the step is far away, don't judge it. + if (m_pPlayerState->m_PlayerController == PC_AUTOPLAY && + fSecondsFromExact > GetWindowSeconds(TW_W5)) { score = TNS_None; break; @@ -2300,36 +2336,36 @@ 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 // 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 // first mine for a row. We know we're the first mine if // there are are no mines to the left of us. - for( int t=0; t= get_to_avoid ) + if (score >= get_to_avoid) return; // avoided else score = TNS_HitMine; } - if( pTN->type == TapNoteType_Attack && score > TNS_W4 ) + if (pTN->type == TapNoteType_Attack && score > TNS_W4) score = TNS_W2; // sentinel /* AI will generate misses here. Don't handle a miss like a regular * note because we want the judgment animation to appear delayed. * Instead, return early if AI generated a miss, and let * UpdateTapNotesMissedOlderThan() detect and handle the misses. */ - if( score == TNS_Miss ) + if (score == TNS_Miss) return; // Put some small, random amount in fNoteOffset so that demonstration @@ -2347,7 +2383,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele float fWindowW5 = GetWindowSeconds(TW_W5); // W1 is the top judgment, there is no overlap. - if( score == TNS_W1 ) + if (score == TNS_W1) fNoteOffset = randomf(-fWindowW1, fWindowW1); else { @@ -2355,25 +2391,25 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele float fLowerBound = 0.0f; // negative upper limit float fUpperBound = 0.0f; // positive lower limit float fCompareWindow = 0.0f; // filled in here: - if( score == TNS_W2 ) + if (score == TNS_W2) { fLowerBound = -fWindowW1; fUpperBound = fWindowW1; fCompareWindow = fWindowW2; } - else if( score == TNS_W3 ) + else if (score == TNS_W3) { fLowerBound = -fWindowW2; fUpperBound = fWindowW2; fCompareWindow = fWindowW3; } - else if( score == TNS_W4 ) + else if (score == TNS_W4) { fLowerBound = -fWindowW3; fUpperBound = fWindowW3; fCompareWindow = fWindowW4; } - else if( score == TNS_W5 ) + else if (score == TNS_W5) { fLowerBound = -fWindowW4; fUpperBound = fWindowW4; @@ -2382,7 +2418,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele float f1 = randomf(-fCompareWindow, fLowerBound); float f2 = randomf(fUpperBound, fCompareWindow); - if(randomf() * 100 >= 50) + if (randomf() * 100 >= 50) fNoteOffset = f1; else fNoteOffset = f2; @@ -2399,6 +2435,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele fNoteOffset = TapNoteOffset attribute break; */ + } default: FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController)); } diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index 081aefb367..f319ff65e8 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -10,6 +10,7 @@ #include "Style.h" #include "CommonMetrics.h" #include +#include static const char *LifeTypeNames[] = { "Bar", @@ -96,6 +97,7 @@ void PlayerOptions::Init() m_bCosecant = false; m_sNoteSkin = ""; m_fVisualDelay = 0.0f; + m_twDisabledWindows.clear(); ZERO( m_fMovesX ); ONE( m_SpeedfMovesX ); ZERO( m_fMovesY ); ONE( m_SpeedfMovesY ); 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_sNoteSkin ); DO_COPY( m_fVisualDelay ); + DO_COPY( m_twDisabledWindows ); #undef APPROACH #undef DO_COPY } @@ -564,6 +567,20 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const // Note that we don't process sub-millisecond visual delay. AddTo.push_back( ssprintf("%.0fms VisualDelay", m_fVisualDelay * 1000.0f) ); } + + if (!m_twDisabledWindows.empty()) { + std::stringstream ss; + ss << "No "; + for (auto it=m_twDisabledWindows.begin(); it != m_twDisabledWindows.end(); ++it) { + if (it != m_twDisabledWindows.begin()) { + ss << "/"; + } + ss << TimingWindowToString(*it).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 @@ -644,6 +661,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut const bool on = (level > 0.5f); static Regex mult("^([0-9]+(\\.[0-9]+)?)x$"); + static Regex disabledWindows("(w[1-5])/{0,1}"); vector matches; if( mult.Compare(sBit, matches) ) { @@ -1142,6 +1160,20 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut else if( sBit == "zbuffer" ) m_bZBuffer = on; else if( sBit == "cosecant" ) m_bCosecant = on; else if( sBit == "visualdelay" ) m_fVisualDelay = level; + else if( disabledWindows.Compare(sBit, matches)) { + for (auto& match : matches) { + static std::map name_to_window = { + {"w1", TW_W1}, + {"w2", TW_W2}, + {"w3", TW_W3}, + {"w4", TW_W4}, + {"w5", TW_W5}, + }; + if (name_to_window.find(match) != name_to_window.end()) { + m_twDisabledWindows.insert(name_to_window[match]); + } + } + } // deprecated mods/left in for compatibility else if( sBit == "converge" ) SET_FLOAT( fScrolls[SCROLL_CENTERED] ) // end of the list @@ -1395,6 +1427,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const return false; } COMPARE(m_fVisualDelay); + COMPARE(m_twDisabledWindows); // != is defined correctly for ordered sets. for( int i = 0; i < PlayerOptions::NUM_ACCELS; ++i ) COMPARE(m_fAccels[i]); for( int i = 0; i < PlayerOptions::NUM_EFFECTS; ++i ) @@ -1461,6 +1494,7 @@ PlayerOptions& PlayerOptions::operator=(PlayerOptions const& other) CPY(m_bZBuffer); CPY(m_bCosecant); CPY(m_fVisualDelay); + CPY(m_twDisabledWindows); CPY_SPEED(fDark); CPY_SPEED(fBlind); CPY_SPEED(fCover); @@ -1693,6 +1727,7 @@ RString PlayerOptions::GetSavedPrefsString() const SAVE( m_bMuteOnError ); SAVE( m_sNoteSkin ); SAVE( m_fVisualDelay ); + SAVE( m_twDisabledWindows ); #undef SAVE return po_prefs.GetString(); } @@ -1741,6 +1776,7 @@ void PlayerOptions::ResetPrefs( ResetPrefsType type ) // Don't clear this. // CPY( m_sNoteSkin ); CPY(m_fVisualDelay); + CPY(m_twDisabledWindows); #undef CPY } @@ -1991,6 +2027,44 @@ public: 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 TNS into the disabled windows set. + p->m_twDisabledWindows.insert(Enum::Check(L, 1)); + } + // Construct a new table indicating all of the disabled windows. + lua_newtable( L ); + for (TimingWindow window : p->m_twDisabledWindows) + { + Enum::Push(L, window); + } + OPTIONAL_RETURN_SELF(original_top); + return 1; + } + + static int ResetDisabledTimingWindows(T* p, lua_State* L) + { + int original_top= lua_gettop(L); + p->m_twDisabledWindows.clear(); + OPTIONAL_RETURN_SELF(original_top); + return 1; + } + + static int GetDisabledTimingWindows(T* p, lua_State* L) + { + int original_top= lua_gettop(L); + lua_newtable( L ); + for (TimingWindow window : p->m_twDisabledWindows) + { + Enum::Push(L, window); + } + OPTIONAL_RETURN_SELF(original_top); + return 1; + } + // NoteSkins static int NoteSkin(T* p, lua_State* L) { @@ -2509,12 +2583,15 @@ public: ADD_MULTICOL_METHOD(Bumpy); ADD_MULTICOL_METHOD(Reverse); - ADD_METHOD(NoteSkin); ADD_METHOD(FailSetting); ADD_METHOD(MinTNSToHideNotes); ADD_METHOD(VisualDelay); + ADD_METHOD(DisableTimingWindow); + ADD_METHOD(ResetDisabledTimingWindows); + ADD_METHOD(GetDisabledTimingWindows); + // Speed ADD_METHOD( CMod ); ADD_METHOD( XMod ); diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h index b2ea2931bf..3ff8d3ec52 100644 --- a/src/PlayerOptions.h +++ b/src/PlayerOptions.h @@ -9,6 +9,8 @@ struct lua_State; #define ONE( arr ) { for( unsigned Z = 0; Z < ARRAYLEN(arr); ++Z ) arr[Z]=1.0f; } +#include + #include "GameConstantsAndTypes.h" #include "PlayerNumber.h" #include "PrefsManager.h" @@ -398,6 +400,12 @@ public: /** @brief The Visual Delay additionally applied on a per-player basis in ms. */ float m_fVisualDelay; + /** @brief The TimingWindow that can be disabled. Valid values are only W1-W5. + * Other values are ignore. + * We use a set instead of unordered_set because we want the ordering so that + * the generated player options string is consistent. */ + std::set m_twDisabledWindows; + void NextAccel(); void NextEffect(); void NextAppearance(); From 29671652352537ba6e872f4e2e9c79367aa3dd38 Mon Sep 17 00:00:00 2001 From: teejusb <5017202+teejusb@users.noreply.github.com> Date: Fri, 10 Dec 2021 21:12:11 -0800 Subject: [PATCH 2/4] Logic fixes --- src/PlayerOptions.cpp | 94 +++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index f319ff65e8..72c26b3e4c 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -661,7 +661,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut const bool on = (level > 0.5f); static Regex mult("^([0-9]+(\\.[0-9]+)?)x$"); - static Regex disabledWindows("(w[1-5])/{0,1}"); + static Regex disabledWindows("(w[1-5])"); vector matches; if( mult.Compare(sBit, matches) ) { @@ -1122,56 +1122,67 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut { if (sBit.find("x") != sBit.npos) { - for (int i=0; i<16; i++) - { - sMod = ssprintf( "movex%d", i+1 ); - if( sBit == sMod) - { - SET_FLOAT( fMovesX[i] ) - break; - } - } + for (int i=0; i<16; i++) + { + sMod = ssprintf( "movex%d", i+1 ); + if( sBit == sMod) + { + SET_FLOAT( fMovesX[i] ) + break; + } + } } else if (sBit.find("y") != sBit.npos) { - for (int i=0; i<16; i++) - { - sMod = ssprintf( "movey%d", i+1 ); - if( sBit == sMod) - { - SET_FLOAT( fMovesY[i] ) - break; - } - } + for (int i=0; i<16; i++) + { + sMod = ssprintf( "movey%d", i+1 ); + if( sBit == sMod) + { + SET_FLOAT( fMovesY[i] ) + break; + } + } } else if (sBit.find("z") != sBit.npos) { - for (int i=0; i<16; i++) - { - sMod = ssprintf( "movez%d", i+1 ); - if( sBit == sMod) - { - SET_FLOAT( fMovesZ[i] ) - break; - } - } + for (int i=0; i<16; i++) + { + sMod = ssprintf( "movez%d", i+1 ); + if( sBit == sMod) + { + SET_FLOAT( fMovesZ[i] ) + break; + } + } } } else if( sBit == "zbuffer" ) m_bZBuffer = on; else if( sBit == "cosecant" ) m_bCosecant = on; else if( sBit == "visualdelay" ) m_fVisualDelay = level; - else if( disabledWindows.Compare(sBit, matches)) { - for (auto& match : matches) { - static std::map name_to_window = { - {"w1", TW_W1}, - {"w2", TW_W2}, - {"w3", TW_W3}, - {"w4", TW_W4}, - {"w5", TW_W5}, - }; - if (name_to_window.find(match) != name_to_window.end()) { - m_twDisabledWindows.insert(name_to_window[match]); + else if( level == 0 && disabledWindows.Compare(sBit)) // "No w1" etc. + { + static std::map nameToWindow = { + {"w1", TW_W1}, + {"w2", TW_W2}, + {"w3", TW_W3}, + {"w4", TW_W4}, + {"w5", TW_W5}, + }; + // 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; + + if (nameToWindow.find(matches[0]) != nameToWindow.end()) + { + m_twDisabledWindows.insert(nameToWindow[matches[0]]); } + input = matches[1]; } } // deprecated mods/left in for compatibility @@ -2032,7 +2043,7 @@ public: int original_top= lua_gettop(L); if (original_top >= 1 && !lua_isnil(L, 1)) { - // Insert the specified TNS into the disabled windows set. + // Insert the specified TimingWindow into the disabled windows set. p->m_twDisabledWindows.insert(Enum::Check(L, 1)); } // Construct a new table indicating all of the disabled windows. @@ -2057,9 +2068,12 @@ public: { int original_top= lua_gettop(L); lua_newtable( L ); + int i = 0; for (TimingWindow window : p->m_twDisabledWindows) { Enum::Push(L, window); + lua_rawseti( L, -2, i+1 ); + ++i; } OPTIONAL_RETURN_SELF(original_top); return 1; From 99ee1f2b84d7980bcb87e5976944f68bf0755a06 Mon Sep 17 00:00:00 2001 From: teejusb <5017202+teejusb@users.noreply.github.com> Date: Tue, 21 Dec 2021 23:44:37 -0800 Subject: [PATCH 3/4] Use bitset instead of map and adjust files accordingly. --- Docs/Luadoc/Lua.xml | 4 ++ Docs/Luadoc/LuaDocumentation.xml | 16 +++++++ src/GameConstantsAndTypes.cpp | 1 + src/Player.cpp | 82 +++++++++++++------------------- src/PlayerAI.cpp | 20 +++++++- src/PlayerOptions.cpp | 58 +++++++++++----------- src/PlayerOptions.h | 11 ++--- 7 files changed, 109 insertions(+), 83 deletions(-) diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index b130b3bcce..292e953e92 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1248,6 +1248,7 @@ + @@ -1267,6 +1268,7 @@ + @@ -1319,6 +1321,7 @@ + @@ -1368,6 +1371,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index cf1592ffa2..5d8a2c28dc 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3915,6 +3915,11 @@ a,b = options:Boost(5) + + Selectively disable specific timing windows for a player.
+ Valid values are W1 to W5 as defined in the enum.
+ Returns a table of TimingWindow with the set of disabled windows after the function call. +
If the player is using Distant (zero skew and positive tilt), returns the value of tilt and its approach_speed.
Returns nil otherwise.
@@ -3960,6 +3965,9 @@ a,b = options:Boost(5)
+ + Returns a table of the currently disabled s for the player. + Returns true if step attacks or random attacks are enabled. @@ -4065,6 +4073,9 @@ prev_note_name, succeeded = options:NoteSkin("cel") + + Re-enable all s that may have previously been disabled. + Use 1-16 in place of 'n' to apply Reverse on a specific column. @@ -4165,6 +4176,11 @@ prev_note_name, succeeded = options:NoteSkin("cel") Returns true if the player is using reverse. (equivalent to GetReverse() == 1.0) + + The time in seconds to adjust a player's visual delay by.
+ Negative values will shift the arrows up, while positive values will push them down.
+ Sub-millisecond visual delay values are not saved and are instead rounded to the closest millisecond. +
diff --git a/src/GameConstantsAndTypes.cpp b/src/GameConstantsAndTypes.cpp index 2c6e4d1363..c1ca5653c5 100644 --- a/src/GameConstantsAndTypes.cpp +++ b/src/GameConstantsAndTypes.cpp @@ -284,6 +284,7 @@ static const char *TimingWindowNames[] = { }; XToString( TimingWindow ); LuaXType( TimingWindow ); +StringToX( TimingWindow ); static const char *ScoreEventNames[] = { "CheckpointHit", diff --git a/src/Player.cpp b/src/Player.cpp index e2ed301697..4b968bf483 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -104,7 +104,7 @@ static const float StepSearchDistance = 1.0f; void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ) { - sNameOut = "TimingWindowSeconds" + TimingWindowToString( (TimingWindow)i ); + sNameOut = "TimingWindowSeconds" + TimingWindowToString( static_cast(i) ); switch( i ) { case TW_W1: @@ -1003,15 +1003,15 @@ void Player::Update( float fDeltaTime ) { float largestWindow = 0.0f; const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; - if (disabledWindows.find(TW_W1) == disabledWindows.end()) + if (!disabledWindows[TW_W1]) largestWindow = max(largestWindow, GetWindowSeconds(TW_W1)); - if (disabledWindows.find(TW_W2) == disabledWindows.end()) + if (!disabledWindows[TW_W2]) largestWindow = max(largestWindow, GetWindowSeconds(TW_W2)); - if (disabledWindows.find(TW_W3) == disabledWindows.end()) + if (!disabledWindows[TW_W3]) largestWindow = max(largestWindow, GetWindowSeconds(TW_W3)); - if (disabledWindows.find(TW_W4) == disabledWindows.end()) + if (!disabledWindows[TW_W4]) largestWindow = max(largestWindow, GetWindowSeconds(TW_W4)); - if (disabledWindows.find(TW_W5) == disabledWindows.end()) + if (!disabledWindows[TW_W5]) largestWindow = max(largestWindow, GetWindowSeconds(TW_W5)); // We have to check the unjudged notes that are within the @@ -2272,15 +2272,15 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele { // Set it to the first non-disabled window. const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; - if (disabledWindows.find(TW_W1) == disabledWindows.end()) + if (!disabledWindows[TW_W1]) score = TNS_W1; - else if (disabledWindows.find(TW_W2) == disabledWindows.end()) + else if (!disabledWindows[TW_W2]) score = TNS_W2; - else if (disabledWindows.find(TW_W3) == disabledWindows.end()) + else if (!disabledWindows[TW_W3]) score = TNS_W3; - else if (disabledWindows.find(TW_W4) == disabledWindows.end()) + else if (!disabledWindows[TW_W4]) score = TNS_W4; - else if (disabledWindows.find(TW_W5) == disabledWindows.end()) + else if (!disabledWindows[TW_W5]) score = TNS_W5; break; @@ -2290,11 +2290,11 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele if( (pTN->type == TapNoteType_Lift) == bRelease ) { const auto &disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; - if( fSecondsFromExact <= GetWindowSeconds(TW_W1) && disabledWindows.find(TW_W1) == disabledWindows.end()) score = TNS_W1; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) && disabledWindows.find(TW_W2) == disabledWindows.end()) score = TNS_W2; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) && disabledWindows.find(TW_W3) == disabledWindows.end()) score = TNS_W3; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) && disabledWindows.find(TW_W4) == disabledWindows.end()) score = TNS_W4; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) && disabledWindows.find(TW_W5) == disabledWindows.end()) score = TNS_W5; + if( fSecondsFromExact <= GetWindowSeconds(TW_W1) && !disabledWindows[TW_W1] ) score = TNS_W1; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) && !disabledWindows[TW_W2] ) score = TNS_W2; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) && !disabledWindows[TW_W3] ) score = TNS_W3; + 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; } @@ -2304,30 +2304,14 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele case PC_AUTOPLAY: { score = PlayerAI::GetTapNoteScore(m_pPlayerState); - const auto& disabledWindows = m_pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; - for (int i = score; i >= TNS_W5; i--) - { - TapNoteScore cur_score = (TapNoteScore)i; - // Downgrade the TapNoteScore if that specific window is disabled. - if (cur_score == TNS_W1 && disabledWindows.find(TW_W1) == disabledWindows.end()) - score = TNS_W2; - else if (cur_score == TNS_W2 && disabledWindows.find(TW_W2) == disabledWindows.end()) - score = TNS_W3; - else if (cur_score == TNS_W3 && disabledWindows.find(TW_W3) == disabledWindows.end()) - score = TNS_W4; - else if (cur_score == TNS_W4 && disabledWindows.find(TW_W4) == disabledWindows.end()) - score = TNS_W5; - else if (cur_score == TNS_W5 && disabledWindows.find(TW_W5) == disabledWindows.end()) - score = TNS_None; - } /* XXX: This doesn't make sense. * Step should only be called in autoplay for hit notes. */ #if 0 - // GetTapNoteScore always returns TNS_W1 in autoplay. - // If the step is far away, don't judge it. - if (m_pPlayerState->m_PlayerController == PC_AUTOPLAY && - fSecondsFromExact > GetWindowSeconds(TW_W5)) + // GetTapNoteScore always returns TNS_W1 in autoplay. + // If the step is far away, don't judge it. + if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY && + fSecondsFromExact > GetWindowSeconds(TW_W5) ) { score = TNS_None; break; @@ -2336,36 +2320,36 @@ 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 // 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 // first mine for a row. We know we're the first mine if // there are are no mines to the left of us. - for (int t = 0; t < col; t++) + for ( int t=0; t= get_to_avoid) + if (score >= get_to_avoid ) return; // avoided else score = TNS_HitMine; } - if (pTN->type == TapNoteType_Attack && score > TNS_W4) + if ( pTN->type == TapNoteType_Attack && score > TNS_W4 ) score = TNS_W2; // sentinel /* AI will generate misses here. Don't handle a miss like a regular * note because we want the judgment animation to appear delayed. * Instead, return early if AI generated a miss, and let * UpdateTapNotesMissedOlderThan() detect and handle the misses. */ - if (score == TNS_Miss) + if ( score == TNS_Miss ) return; // Put some small, random amount in fNoteOffset so that demonstration @@ -2383,7 +2367,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele float fWindowW5 = GetWindowSeconds(TW_W5); // W1 is the top judgment, there is no overlap. - if (score == TNS_W1) + if ( score == TNS_W1 ) fNoteOffset = randomf(-fWindowW1, fWindowW1); else { @@ -2391,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 fUpperBound = 0.0f; // positive lower limit float fCompareWindow = 0.0f; // filled in here: - if (score == TNS_W2) + if ( score == TNS_W2 ) { fLowerBound = -fWindowW1; fUpperBound = fWindowW1; fCompareWindow = fWindowW2; } - else if (score == TNS_W3) + else if ( score == TNS_W3 ) { fLowerBound = -fWindowW2; fUpperBound = fWindowW2; fCompareWindow = fWindowW3; } - else if (score == TNS_W4) + else if ( score == TNS_W4 ) { fLowerBound = -fWindowW3; fUpperBound = fWindowW3; fCompareWindow = fWindowW4; } - else if (score == TNS_W5) + else if ( score == TNS_W5 ) { fLowerBound = -fWindowW4; fUpperBound = fWindowW4; @@ -2418,7 +2402,7 @@ void Player::Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRele float f1 = randomf(-fCompareWindow, fLowerBound); float f2 = randomf(fUpperBound, fCompareWindow); - if (randomf() * 100 >= 50) + if(randomf() * 100 >= 50) fNoteOffset = f1; else fNoteOffset = f2; diff --git a/src/PlayerAI.cpp b/src/PlayerAI.cpp index 44cf43e706..418f40147b 100644 --- a/src/PlayerAI.cpp +++ b/src/PlayerAI.cpp @@ -141,7 +141,25 @@ TapNoteScore PlayerAI::GetTapNoteScore( const PlayerState* pPlayerState ) TapScoreDistribution& distribution = g_Distributions[iCpuSkill]; - return distribution.GetTapNoteScore(); + TapNoteScore score = distribution.GetTapNoteScore(); + + const auto& disabledWindows = pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; + for (int i = score; i >= TNS_W5; i--) + { + TapNoteScore cur_score = (TapNoteScore)i; + // Downgrade the TapNoteScore if that specific window is disabled. + if (cur_score == TNS_W1 && disabledWindows[TW_W1]) + score = TNS_W2; + if (cur_score == TNS_W2 && disabledWindows[TW_W2]) + score = TNS_W3; + if (cur_score == TNS_W3 && disabledWindows[TW_W3]) + score = TNS_W4; + if (cur_score == TNS_W4 && disabledWindows[TW_W4]) + score = TNS_W5; + if (cur_score == TNS_W5 && disabledWindows[TW_W5]) + score = TNS_None; + } + return score; } /* diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index 72c26b3e4c..e268d60203 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -97,7 +97,7 @@ void PlayerOptions::Init() m_bCosecant = false; m_sNoteSkin = ""; m_fVisualDelay = 0.0f; - m_twDisabledWindows.clear(); + m_twDisabledWindows.reset(); ZERO( m_fMovesX ); ONE( m_SpeedfMovesX ); ZERO( m_fMovesY ); ONE( m_SpeedfMovesY ); ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ ); @@ -568,14 +568,19 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const AddTo.push_back( ssprintf("%.0fms VisualDelay", m_fVisualDelay * 1000.0f) ); } - if (!m_twDisabledWindows.empty()) { + if (m_twDisabledWindows.count() != 0) { std::stringstream ss; + bool is_first = true; ss << "No "; - for (auto it=m_twDisabledWindows.begin(); it != m_twDisabledWindows.end(); ++it) { - if (it != m_twDisabledWindows.begin()) { - ss << "/"; + 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(i)).c_str(); } - ss << TimingWindowToString(*it).c_str(); } // Final string will be something like "No W4/W5" @@ -1162,13 +1167,6 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut else if( sBit == "visualdelay" ) m_fVisualDelay = level; else if( level == 0 && disabledWindows.Compare(sBit)) // "No w1" etc. { - static std::map nameToWindow = { - {"w1", TW_W1}, - {"w2", TW_W2}, - {"w3", TW_W3}, - {"w4", TW_W4}, - {"w5", TW_W5}, - }; // 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])(.*)$"); @@ -1178,9 +1176,11 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut if (!allDisabledWindows.Compare(input, matches)) break; - if (nameToWindow.find(matches[0]) != nameToWindow.end()) + TimingWindow tw; + bool ret = StringConversion::FromString(matches[0].MakeUpper(), tw); + if (ret && TW_W1 <= tw && tw <= TW_W5) { - m_twDisabledWindows.insert(nameToWindow[matches[0]]); + m_twDisabledWindows.set(tw); } input = matches[1]; } @@ -2044,13 +2044,17 @@ public: if (original_top >= 1 && !lua_isnil(L, 1)) { // Insert the specified TimingWindow into the disabled windows set. - p->m_twDisabledWindows.insert(Enum::Check(L, 1)); + p->m_twDisabledWindows.set(Enum::Check(L, 1)); } // Construct a new table indicating all of the disabled windows. lua_newtable( L ); - for (TimingWindow window : p->m_twDisabledWindows) - { - Enum::Push(L, window); + int j = 0; + for (int i=TW_W1; i != TW_W5; ++i) { + if (p->m_twDisabledWindows[i]) { + Enum::Push(L, static_cast(i)); + lua_rawseti( L, -2, j+1 ); + ++j; + } } OPTIONAL_RETURN_SELF(original_top); return 1; @@ -2059,7 +2063,7 @@ public: static int ResetDisabledTimingWindows(T* p, lua_State* L) { int original_top= lua_gettop(L); - p->m_twDisabledWindows.clear(); + p->m_twDisabledWindows.reset(); OPTIONAL_RETURN_SELF(original_top); return 1; } @@ -2068,14 +2072,14 @@ public: { int original_top= lua_gettop(L); lua_newtable( L ); - int i = 0; - for (TimingWindow window : p->m_twDisabledWindows) - { - Enum::Push(L, window); - lua_rawseti( L, -2, i+1 ); - ++i; + int j = 0; + for (int i=TW_W1; i != TW_W5; ++i) { + if (p->m_twDisabledWindows[i]) { + Enum::Push(L, static_cast(i)); + lua_rawseti( L, -2, j+1 ); + ++j; + } } - OPTIONAL_RETURN_SELF(original_top); return 1; } diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h index 3ff8d3ec52..6a0dfd9ad5 100644 --- a/src/PlayerOptions.h +++ b/src/PlayerOptions.h @@ -9,7 +9,7 @@ struct lua_State; #define ONE( arr ) { for( unsigned Z = 0; Z < ARRAYLEN(arr); ++Z ) arr[Z]=1.0f; } -#include +#include #include "GameConstantsAndTypes.h" #include "PlayerNumber.h" @@ -400,11 +400,10 @@ public: /** @brief The Visual Delay additionally applied on a per-player basis in ms. */ float m_fVisualDelay; - /** @brief The TimingWindow that can be disabled. Valid values are only W1-W5. - * Other values are ignore. - * We use a set instead of unordered_set because we want the ordering so that - * the generated player options string is consistent. */ - std::set m_twDisabledWindows; + /** @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 NextEffect(); From a48a5b73ad556175551462036c990e4f5790abaf Mon Sep 17 00:00:00 2001 From: teejusb <5017202+teejusb@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:21:28 -0800 Subject: [PATCH 4/4] Remove returned timing window table --- Docs/Luadoc/LuaDocumentation.xml | 5 ++--- src/PlayerAI.cpp | 26 +++++++++++--------------- src/PlayerOptions.cpp | 10 ---------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 5d8a2c28dc..65ebc05efd 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3915,10 +3915,9 @@ a,b = options:Boost(5)
- + Selectively disable specific timing windows for a player.
- Valid values are W1 to W5 as defined in the enum.
- Returns a table of TimingWindow with the set of disabled windows after the function call. + Valid values are W1 to W5 as defined in the enum.
If the player is using Distant (zero skew and positive tilt), returns the value of tilt and its approach_speed.
diff --git a/src/PlayerAI.cpp b/src/PlayerAI.cpp index 418f40147b..62ded975d3 100644 --- a/src/PlayerAI.cpp +++ b/src/PlayerAI.cpp @@ -144,21 +144,17 @@ TapNoteScore PlayerAI::GetTapNoteScore( const PlayerState* pPlayerState ) TapNoteScore score = distribution.GetTapNoteScore(); const auto& disabledWindows = pPlayerState->m_PlayerOptions.GetCurrent().m_twDisabledWindows; - for (int i = score; i >= TNS_W5; i--) - { - TapNoteScore cur_score = (TapNoteScore)i; - // Downgrade the TapNoteScore if that specific window is disabled. - if (cur_score == TNS_W1 && disabledWindows[TW_W1]) - score = TNS_W2; - if (cur_score == TNS_W2 && disabledWindows[TW_W2]) - score = TNS_W3; - if (cur_score == TNS_W3 && disabledWindows[TW_W3]) - score = TNS_W4; - if (cur_score == TNS_W4 && disabledWindows[TW_W4]) - score = TNS_W5; - if (cur_score == TNS_W5 && disabledWindows[TW_W5]) - score = TNS_None; - } + // 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; } diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index e268d60203..cd54760766 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -2046,16 +2046,6 @@ public: // Insert the specified TimingWindow into the disabled windows set. p->m_twDisabledWindows.set(Enum::Check(L, 1)); } - // Construct a new table indicating all of the disabled windows. - 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(i)); - lua_rawseti( L, -2, j+1 ); - ++j; - } - } OPTIONAL_RETURN_SELF(original_top); return 1; }