From 8f4eafea4eab7a1a2e7d4b3571732477509ba0cf Mon Sep 17 00:00:00 2001
From: Andrei Zavatski <megaman9919@gmail.com>
Date: Tue, 7 Jan 2025 14:00:31 +0300
Subject: [PATCH] Fix combo properties multiple reassignments

---
 .../Objects/CatchHitObject.cs                 | 36 ++++++++++---------
 osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 36 ++++++++++---------
 .../Objects/Types/IHasComboInformation.cs     | 16 +++++----
 3 files changed, 48 insertions(+), 40 deletions(-)

diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 2018fd5ea9..3c7ead09af 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -159,27 +159,29 @@ namespace osu.Game.Rulesets.Catch.Objects
         {
             // Note that this implementation is shared with the osu! ruleset's implementation.
             // If a change is made here, OsuHitObject.cs should also be updated.
-            ComboIndex = lastObj?.ComboIndex ?? 0;
-            ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
-            IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
+            int index = lastObj?.ComboIndex ?? 0;
+            int indexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
+            int inCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
 
-            if (this is BananaShower)
+            // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so.
+            if (this is not BananaShower)
             {
-                // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so.
-                return;
+                // At decode time, the first hitobject in the beatmap and the first hitobject after a banana shower are both enforced to be a new combo,
+                // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here.
+                if (NewCombo || lastObj == null || lastObj is BananaShower)
+                {
+                    inCurrentCombo = 0;
+                    index++;
+                    indexWithOffsets += ComboOffset + 1;
+
+                    if (lastObj != null)
+                        lastObj.LastInCombo = true;
+                }
             }
 
-            // At decode time, the first hitobject in the beatmap and the first hitobject after a banana shower are both enforced to be a new combo,
-            // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here.
-            if (NewCombo || lastObj == null || lastObj is BananaShower)
-            {
-                IndexInCurrentCombo = 0;
-                ComboIndex++;
-                ComboIndexWithOffsets += ComboOffset + 1;
-
-                if (lastObj != null)
-                    lastObj.LastInCombo = true;
-            }
+            ComboIndex = index;
+            ComboIndexWithOffsets = indexWithOffsets;
+            IndexInCurrentCombo = inCurrentCombo;
         }
 
         protected override HitWindows CreateHitWindows() => HitWindows.Empty;
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 8c1bd6302e..937e0bda23 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -184,27 +184,29 @@ namespace osu.Game.Rulesets.Osu.Objects
         {
             // Note that this implementation is shared with the osu!catch ruleset's implementation.
             // If a change is made here, CatchHitObject.cs should also be updated.
-            ComboIndex = lastObj?.ComboIndex ?? 0;
-            ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
-            IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
+            int index = lastObj?.ComboIndex ?? 0;
+            int indexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
+            int inCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
 
-            if (this is Spinner)
+            // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so.
+            if (this is not Spinner)
             {
-                // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so.
-                return;
+                // At decode time, the first hitobject in the beatmap and the first hitobject after a spinner are both enforced to be a new combo,
+                // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here.
+                if (NewCombo || lastObj == null || lastObj is Spinner)
+                {
+                    inCurrentCombo = 0;
+                    index++;
+                    indexWithOffsets += ComboOffset + 1;
+
+                    if (lastObj != null)
+                        lastObj.LastInCombo = true;
+                }
             }
 
-            // At decode time, the first hitobject in the beatmap and the first hitobject after a spinner are both enforced to be a new combo,
-            // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here.
-            if (NewCombo || lastObj == null || lastObj is Spinner)
-            {
-                IndexInCurrentCombo = 0;
-                ComboIndex++;
-                ComboIndexWithOffsets += ComboOffset + 1;
-
-                if (lastObj != null)
-                    lastObj.LastInCombo = true;
-            }
+            ComboIndex = index;
+            ComboIndexWithOffsets = indexWithOffsets;
+            IndexInCurrentCombo = inCurrentCombo;
         }
 
         protected override HitWindows CreateHitWindows() => new OsuHitWindows();
diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
index 3aa68197ec..98519de981 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
@@ -84,19 +84,23 @@ namespace osu.Game.Rulesets.Objects.Types
         /// <param name="lastObj">The previous hitobject, or null if this is the first object in the beatmap.</param>
         void UpdateComboInformation(IHasComboInformation? lastObj)
         {
-            ComboIndex = lastObj?.ComboIndex ?? 0;
-            ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
-            IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
+            int index = lastObj?.ComboIndex ?? 0;
+            int indexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
+            int inCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
 
             if (NewCombo || lastObj == null)
             {
-                IndexInCurrentCombo = 0;
-                ComboIndex++;
-                ComboIndexWithOffsets += ComboOffset + 1;
+                inCurrentCombo = 0;
+                index++;
+                indexWithOffsets += ComboOffset + 1;
 
                 if (lastObj != null)
                     lastObj.LastInCombo = true;
             }
+
+            ComboIndex = index;
+            ComboIndexWithOffsets = indexWithOffsets;
+            IndexInCurrentCombo = inCurrentCombo;
         }
     }
 }