diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 7d3725e625..b61eea1669 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -313,6 +313,7 @@ EffectsReceptor=Add alternative behaviors to the receptors and notes. EffectsArrow=Add alternative behaviors to the receptors and notes. EnableAttackSounds=If enabled, a sound effect will play when an attack begins or ends. EnableMineHitSound=If enabled, a sound will play when a mine is stepped on. +EnableNoteskinHitSounds=If enabled, noteskin hitsounds will play if available. EnablePitchRates=If enabled, changing the rate of a song will change the pitch accordingly. If not enabled, the pitch is not modified. EventMode=When set to &oq;ON&cq;, and you can continue playing forever. Failing will also take you back to the the Select Music or Select Course screen. Exit= @@ -905,6 +906,7 @@ EffectsReceptor=Effects EffectsArrow= EnableAttackSounds=Attack Sounds EnableMineHitSound=Mine Sounds +EnableNoteskinHitSounds=Noteskin HitSounds EnablePitchRates=Pitch On Rates Entry %d=Entry %d EventMode=Event Mode diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 991db8ab25..4b9b724dc7 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -2186,7 +2186,7 @@ Line21="conf,Center1Player" Fallback="ScreenOptionsServiceChild" NextScreen="ScreenOptionsService" PrevScreen="ScreenOptionsService" -LineNames="1,2,3,RefreshRate,FSType,4,5,6,7,8,9,11,13,FNR,14,17,FRGlobal,FRGameplay,19,20,GO" +LineNames="1,2,3,RefreshRate,FSType,4,5,6,7,8,9,11,13,FNR,14,17,FRGlobal,FRGameplay,19,20,21,GO" Line1="lua,ConfDisplayMode()" Line2="lua,ConfAspectRatio()" Line3="lua,ConfDisplayResolution()" @@ -2206,9 +2206,10 @@ Line14="conf,ShowStats" Line16="conf,AttractSoundFrequency" Line17="lua,SoundVolumeControl()" Line19="conf,EnableMineHitSound" +Line20="conf,EnableNoteskinHitSounds" LineFRGlobal="lua,FrameLimitGlobal()" LineFRGameplay="lua,FrameLimitGameplay()" -Line20="lua,VisualDelaySeconds()" +Line21="lua,VisualDelaySeconds()" LineGO="lua,GlobalOffsetSeconds()" [ScreenOptionsAdvanced] diff --git a/src/Etterna/Actor/Gameplay/Player.cpp b/src/Etterna/Actor/Gameplay/Player.cpp index 00c4f0deee..43de64b3f1 100644 --- a/src/Etterna/Actor/Gameplay/Player.cpp +++ b/src/Etterna/Actor/Gameplay/Player.cpp @@ -97,6 +97,7 @@ static Preference m_fTimingWindowScale("TimingWindowScale", 1.0F); static Preference1D m_fTimingWindowSeconds(TimingWindowSecondsInit, NUM_TimingWindow); static Preference g_bEnableMineSoundPlayback("EnableMineHitSound", true); +static Preference g_bEnableNoteskinHitsounds("EnableNoteskinHitSounds", true); // moved out of being members of player.h static ThemeMetric GRAY_ARROWS_Y_STANDARD; @@ -280,6 +281,9 @@ Player::~Player() REPLAYS->ReleaseReplay(pbReplay); SAFE_DELETE(m_pNoteField); + for (auto& [judge, hitsound] : m_mHitsounds) { + SAFE_DELETE(hitsound); + } for (unsigned i = 0; i < m_vpHoldJudgment.size(); ++i) { SAFE_DELETE(m_vpHoldJudgment[i]); } @@ -453,6 +457,32 @@ Player::Init(const std::string& sType, ActorUtil::LoadAllCommands(*m_pNoteField, sType); this->AddChild(m_pNoteField); } + + // Load noteskin hitsounds -Creosm + { + if (g_bEnableNoteskinHitsounds) { + auto NoteskinLock = LockNoteSkin( m_pPlayerState->m_PlayerOptions.GetStage().m_sNoteSkin ); + int TNSIndex = TNS_W1; + + for (std::string JudgementName : {"Marvelous", "Perfect", "Great", "Good", "Bad", "Miss"}) { + + auto MetricJudgementHitsoundPath = NOTESKIN->GetMetric("Hitsound", JudgementName); + auto JudgeHitsoundPath = NOTESKIN->GetPath("", MetricJudgementHitsoundPath); + + if (JudgeHitsoundPath != "") { + Locator::getLogger()->info("Found noteskin hitsound metric for judgement: {} using path: {}", JudgementName, JudgeHitsoundPath); + + auto JudgeHitsound = new RageSound(); + JudgeHitsound->Load(JudgeHitsoundPath, true); + m_mHitsounds[static_cast(TNSIndex)] = JudgeHitsound; + }; + + TNSIndex--; + + } + } + } + } /** * @brief Determine if a TapNote needs a tap note style judgment. @@ -568,7 +598,7 @@ Player::Load() const HighScore* pb = SCOREMAN->GetChartPBAt( GAMESTATE->m_pCurSteps->GetChartKey(), GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); - + // the latter condition checks for Grade_Failed, NUM_Grade, Grade_Invalid if (pb == nullptr || pb->GetGrade() >= Grade_Failed) { wifescorepersonalbest = m_pPlayerState->playertargetgoal; @@ -1806,6 +1836,15 @@ Player::ScoreAllActiveHoldsLetGo() } } +void +Player::PlayHitsound(TapNoteScore tns) +{ + if (g_bEnableNoteskinHitsounds && m_mHitsounds.contains(tns)) + { + m_mHitsounds[tns]->Play(false); + } +} + void Player::PlayKeysound(const TapNote& tn, TapNoteScore score) { @@ -2134,6 +2173,10 @@ Player::Step(int col, score = TNS_W5; } } + + PlayHitsound(score); // Misses are done in + // Player::UpdateTapNotesMissedOlderThan + // -Creosm break; } break; @@ -2597,6 +2640,7 @@ Player::UpdateTapNotesMissedOlderThan(float fMissIfOlderThanSeconds) } } else { tn.result.tns = TNS_Miss; + PlayHitsound(TNS_Miss); // avoid scoring notes that get passed when seeking in pm // not sure how many rows grace time is needed (if any?) diff --git a/src/Etterna/Actor/Gameplay/Player.h b/src/Etterna/Actor/Gameplay/Player.h index df2b7886c7..53fbc7cd82 100644 --- a/src/Etterna/Actor/Gameplay/Player.h +++ b/src/Etterna/Actor/Gameplay/Player.h @@ -193,6 +193,8 @@ class Player : public ActorFrame void DrawHoldJudgments(); void SendComboMessages(unsigned int iOldCombo, unsigned int iOldMissCombo) const; + + void PlayHitsound(TapNoteScore tns); void PlayKeysound(const TapNote& tn, TapNoteScore score); void SetMineJudgment(TapNoteScore tns, int iTrack, int iRow); @@ -286,6 +288,8 @@ class Player : public ActorFrame RageSound m_soundMine; + std::map m_mHitsounds; + std::vector m_vKeysounds; bool m_bSendJudgmentAndComboMessages; diff --git a/src/Etterna/Actor/Gameplay/PlayerReplay.cpp b/src/Etterna/Actor/Gameplay/PlayerReplay.cpp index a98231bda0..72f615f733 100644 --- a/src/Etterna/Actor/Gameplay/PlayerReplay.cpp +++ b/src/Etterna/Actor/Gameplay/PlayerReplay.cpp @@ -367,7 +367,7 @@ PlayerReplay::CheckForSteps(const std::chrono::steady_clock::time_point& tm) const auto fSongBeat = GAMESTATE->m_Position.m_fSongBeat; const auto iSongRow = BeatToNoteRow(fSongBeat); - + std::vector evts; std::vector rows; const auto it = playbackEvents.lower_bound(ARBITRARY_MIN_GAMEPLAY_NUMBER); @@ -628,6 +628,7 @@ PlayerReplay::UpdateTapNotesMissedOlderThan(float fMissIfOlderThanSeconds) m_pPrimaryScoreKeeper->HandleTapScore(tn); } else { tn.result.tns = TNS_Miss; + PlayHitsound(TNS_Miss); if (GAMESTATE->CountNotesSeparately()) { SetJudgment(iter.Row(), iter.Track(), tn); @@ -840,6 +841,8 @@ PlayerReplay::Step(int col, } } + PlayHitsound(score); + // Do game-specific and mode-specific score mapping. score = GAMESTATE->GetCurrentGame()->MapTapNoteScore(score); diff --git a/src/Etterna/Screen/Options/ScreenOptionsMasterPrefs.cpp b/src/Etterna/Screen/Options/ScreenOptionsMasterPrefs.cpp index c47192672a..4e4b149995 100644 --- a/src/Etterna/Screen/Options/ScreenOptionsMasterPrefs.cpp +++ b/src/Etterna/Screen/Options/ScreenOptionsMasterPrefs.cpp @@ -995,6 +995,7 @@ InitializeConfOptions() ADD(c); } ADD(ConfOption("EnableMineHitSound", MovePref, "No", "Yes")); + ADD(ConfOption("EnableNoteskinHitSounds", MovePref, "No", "Yes")); ADD(ConfOption("Invalid", MoveNop, "|Invalid option")); }