@@ -55,12 +55,25 @@ protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBe
5555 return masterGameplayClockContainer ;
5656 }
5757
58+ protected override void LoadAsyncComplete ( )
59+ {
60+ base . LoadAsyncComplete ( ) ;
61+
62+ if ( ! LoadedBeatmapSuccessfully )
63+ return ;
64+
65+ // This hack needs to be called to install its hooks before drawable hit objects get the chance to run update logic,
66+ // because it will not work otherwise due to being too late (various effects of the objects getting missed will have already taken place).
67+ preventMissOnPreviousHitObjects ( ) ;
68+ }
69+
5870 protected override void LoadComplete ( )
5971 {
6072 base . LoadComplete ( ) ;
6173
74+ // this will notify components such as the skin's combo counter, which needs to happen on the update thread
75+ // and therefore can't happen alongside `preventMissOnPreviousHitObjects()` in `LoadAsyncComplete()`
6276 markPreviousObjectsHit ( ) ;
63- markVisibleDrawableObjectsHit ( ) ;
6477
6578 ScoreProcessor . HasCompleted . BindValueChanged ( completed =>
6679 {
@@ -111,38 +124,45 @@ static IEnumerable<HitObject> enumerateHitObjects(IEnumerable<HitObject> hitObje
111124 }
112125 }
113126
114- private void markVisibleDrawableObjectsHit ( )
127+ private void preventMissOnPreviousHitObjects ( )
115128 {
116- if ( ! DrawableRuleset . Playfield . IsLoaded )
129+ void preventMiss ( HitObject hitObject )
117130 {
118- Schedule ( markVisibleDrawableObjectsHit ) ;
119- return ;
131+ var drawableObject = DrawableRuleset . Playfield . HitObjectContainer
132+ . AliveObjects
133+ . SingleOrDefault ( it => it . HitObject == hitObject ) ;
134+
135+ if ( drawableObject != null )
136+ preventMissOnDrawable ( drawableObject ) ;
120137 }
121138
122- foreach ( var drawableObject in enumerateDrawableObjects ( DrawableRuleset . Playfield . AllHitObjects , editorState . Time ) )
139+ void preventMissOnDrawable ( DrawableHitObject drawableObject )
123140 {
124- if ( drawableObject . Entry == null )
125- continue ;
141+ foreach ( var nested in drawableObject . NestedHitObjects )
142+ preventMissOnDrawable ( nested ) ;
126143
127- var result = drawableObject . CreateResult ( drawableObject . HitObject . Judgement ) ;
128- result . Type = result . Judgement . MaxResult ;
129- drawableObject . Entry . Result = result ;
144+ if ( drawableObject . Entry != null && drawableObject . HitObject . GetEndTime ( ) < editorState . Time )
145+ {
146+ var result = drawableObject . CreateResult ( drawableObject . HitObject . Judgement ) ;
147+ result . Type = result . Judgement . MaxResult ;
148+ drawableObject . Entry . Result = result ;
149+ }
130150 }
131151
132- static IEnumerable < DrawableHitObject > enumerateDrawableObjects ( IEnumerable < DrawableHitObject > drawableObjects , double cutoffTime )
152+ void removeListener ( )
133153 {
134- foreach ( var drawableObject in drawableObjects )
154+ if ( ! DrawableRuleset . Playfield . IsLoaded )
135155 {
136- foreach ( var nested in enumerateDrawableObjects ( drawableObject . NestedHitObjects , cutoffTime ) )
137- {
138- if ( nested . HitObject . GetEndTime ( ) < cutoffTime )
139- yield return nested ;
140- }
141-
142- if ( drawableObject . HitObject . GetEndTime ( ) < cutoffTime )
143- yield return drawableObject ;
156+ Schedule ( removeListener ) ;
157+ return ;
144158 }
159+
160+ DrawableRuleset . Playfield . HitObjectUsageBegan -= preventMiss ;
145161 }
162+
163+ DrawableRuleset . Playfield . HitObjectUsageBegan += preventMiss ;
164+
165+ Schedule ( removeListener ) ;
146166 }
147167
148168 protected override void PrepareReplay ( )
0 commit comments