diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index e2fc4d14f6..803927bc6f 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -27,9 +27,9 @@
-
-
-
+
+
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index ab0afb08d7..c7ea29f8c0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -74,42 +74,42 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private void initialiseHyperDash(List objects)
{
- // todo: add difficulty adjust.
- double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
+ List objectWithDroplets = new List();
+ foreach (var currentObject in objects)
+ {
+ if (currentObject is Fruit)
+ objectWithDroplets.Add(currentObject);
+ if (currentObject is JuiceStream)
+ foreach (var currentJuiceElement in currentObject.NestedHitObjects)
+ if (!(currentJuiceElement is TinyDroplet))
+ objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
+ }
+
+ objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+
+ double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
- int objCount = objects.Count;
-
- for (int i = 0; i < objCount - 1; i++)
+ for (int i = 0; i < objectWithDroplets.Count - 1; i++)
{
- CatchHitObject currentObject = objects[i];
-
- // not needed?
- // if (currentObject is TinyDroplet) continue;
-
- CatchHitObject nextObject = objects[i + 1];
-
- // while (nextObject is TinyDroplet)
- // {
- // if (++i == objCount - 1) break;
- // nextObject = objects[i + 1];
- // }
+ CatchHitObject currentObject = objectWithDroplets[i];
+ CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
- double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
+ double timeToNext = nextObject.StartTime - currentObject.StartTime;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
-
- if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
+ float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
+ if (distanceToHyper < 0)
{
currentObject.HyperDashTarget = nextObject;
lastExcess = halfCatcherWidth;
}
else
{
- //currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
- lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
+ currentObject.DistanceToHyperDash = distanceToHyper;
+ lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
}
lastDirection = thisDirection;
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 621fc100c2..0c50dae9fb 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public int IndexInBeatmap { get; set; }
- public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
+ public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4);
public virtual bool NewCombo { get; set; }
@@ -27,7 +27,9 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; }
///
- /// The distance for a fruit to to next hyper if it's not a hyper.
+ /// Difference between the distance to the next object
+ /// and the distance that would have triggered a hyper dash.
+ /// A value close to 0 indicates a difficult jump (for difficulty calculation).
///
public float DistanceToHyperDash { get; set; }
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index d49be69856..102ec7fb3b 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly CatcherArea catcherArea;
+ protected override bool UserScrollSpeedAdjustment => false;
+
public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation)
: base(BASE_WIDTH)
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 9460512a8d..be56ccf8c1 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -107,6 +107,11 @@ namespace osu.Game.Rulesets.Catch.UI
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
+ public static float GetCatcherSize(BeatmapDifficulty difficulty)
+ {
+ return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
public class Catcher : Container, IKeyBindingHandler
{
///
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
new file mode 100644
index 0000000000..440b314e5f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Transform";
+ public override string ShortenedName => "TR";
+ public override FontAwesome Icon => FontAwesome.fa_arrows;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Everything rotates. EVERYTHING.";
+ public override double ScoreMultiplier => 1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
+
+ private float theta;
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables)
+ {
+ var hitObject = (OsuHitObject) drawable.HitObject;
+
+ float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
+
+ Vector2 originalPosition = drawable.Position;
+ Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
+
+ //the - 1 and + 1 prevents the hit objects to appear in the wrong position.
+ double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
+ double moveDuration = hitObject.TimePreempt + 1;
+
+ using (drawable.BeginAbsoluteSequence(appearTime, true))
+ {
+ drawable
+ .MoveToOffset(appearOffset)
+ .MoveTo(originalPosition, moveDuration, Easing.InOutSine);
+ }
+
+ theta += (float) hitObject.TimeFadeIn / 1000;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
new file mode 100644
index 0000000000..2e601c9078
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -0,0 +1,67 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Wiggle";
+ public override string ShortenedName => "WG";
+ public override FontAwesome Icon => FontAwesome.fa_certificate;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "They just won't stay still...";
+ public override double ScoreMultiplier => 1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
+
+ private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
+ private const int wiggle_strength = 10; // Higher = stronger wiggles
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables)
+ drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState;
+ }
+
+ private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state)
+ {
+ var osuObject = (OsuHitObject)drawable.HitObject;
+ Vector2 origin = drawable.Position;
+
+ Random objRand = new Random((int)osuObject.StartTime);
+
+ // Wiggle all objects during TimePreempt
+ int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration;
+
+ void wiggle()
+ {
+ float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
+ float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
+ drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
+ }
+
+ for (int i = 0; i < amountWiggles; i++)
+ using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
+ wiggle();
+
+ // Keep wiggling sliders and spinners for their duration
+ if (!(osuObject is IHasEndTime endTime))
+ return;
+
+ amountWiggles = (int)(endTime.Duration / wiggle_duration);
+
+ for (int i = 0; i < amountWiggles; i++)
+ using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
+ wiggle();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index fa6e9a018a..6736d10dab 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -117,6 +117,11 @@ namespace osu.Game.Rulesets.Osu
new OsuModRelax(),
new OsuModAutopilot(),
};
+ case ModType.Fun:
+ return new Mod[] {
+ new OsuModTransform(),
+ new OsuModWiggle(),
+ };
default:
return new Mod[] { };
}
diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
index b98875cd6a..f31a687d2d 100644
--- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
+++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
@@ -1,9 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Input.EventArgs;
using osu.Framework.MathUtils;
+using osu.Framework.Timing;
using osu.Game.Screens.Play;
using OpenTK.Input;
@@ -12,21 +17,30 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseKeyCounter : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(KeyCounterKeyboard),
+ typeof(KeyCounterMouse),
+ typeof(KeyCounterCollection)
+ };
+
public TestCaseKeyCounter()
{
+ KeyCounterKeyboard rewindTestKeyCounterKeyboard;
KeyCounterCollection kc = new KeyCounterCollection
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
- new KeyCounterKeyboard(Key.Z),
+ rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
},
};
+
AddStep("Add random", () =>
{
Key key = (Key)((int)Key.A + RNG.Next(26));
@@ -34,7 +48,57 @@ namespace osu.Game.Tests.Visual
});
AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
+ Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
+ double time1 = 0;
+
+ AddStep($"Press {testKey} key", () =>
+ {
+ rewindTestKeyCounterKeyboard.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = testKey, Repeat = false });
+ rewindTestKeyCounterKeyboard.TriggerOnKeyUp(null, new KeyUpEventArgs { Key = testKey });
+ });
+
+ AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
+
+ AddStep($"Press {testKey} key", () =>
+ {
+ rewindTestKeyCounterKeyboard.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = testKey, Repeat = false });
+ rewindTestKeyCounterKeyboard.TriggerOnKeyUp(null, new KeyUpEventArgs { Key = testKey });
+ time1 = Clock.CurrentTime;
+ });
+
+ AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
+
+ IFrameBasedClock oldClock = null;
+
+ AddStep($"Rewind {testKey} counter once", () =>
+ {
+ oldClock = rewindTestKeyCounterKeyboard.Clock;
+ rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
+ });
+
+ AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
+
+ AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
+
+ AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
+
+ AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
+
Add(kc);
}
+
+ private class FixedClock : IClock
+ {
+ private readonly double time;
+
+ public FixedClock(double time)
+ {
+ this.time = time;
+ }
+
+ public double CurrentTime => time;
+ public double Rate => 1;
+ public bool IsRunning => false;
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index 9d7cd673dc..9db2c5f08e 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
@@ -44,6 +45,25 @@ namespace osu.Game.Beatmaps
public virtual void PostProcess()
{
+ void updateNestedCombo(HitObject obj, int comboIndex, int indexInCurrentCombo)
+ {
+ if (obj is IHasComboInformation objectComboInfo)
+ {
+ objectComboInfo.ComboIndex = comboIndex;
+ objectComboInfo.IndexInCurrentCombo = indexInCurrentCombo;
+ foreach (var nestedObject in obj.NestedHitObjects)
+ updateNestedCombo(nestedObject, comboIndex, indexInCurrentCombo);
+ }
+ }
+
+ foreach (var hitObject in Beatmap.HitObjects)
+ {
+ if (hitObject is IHasComboInformation objectComboInfo)
+ {
+ foreach (var nested in hitObject.NestedHitObjects)
+ updateNestedCombo(nested, objectComboInfo.ComboIndex, objectComboInfo.IndexInCurrentCombo);
+ }
+ }
}
}
}
diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs
index 2ff901deb1..103a999591 100644
--- a/osu.Game/Online/Chat/InfoMessage.cs
+++ b/osu.Game/Online/Chat/InfoMessage.cs
@@ -15,11 +15,7 @@ namespace osu.Game.Online.Chat
Timestamp = DateTimeOffset.Now;
Content = message;
- Sender = new User
- {
- Username = @"system",
- Colour = @"0000ff",
- };
+ Sender = User.SYSTEM_USER;
}
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 6fcb948298..8610a8d74d 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -355,7 +355,7 @@ namespace osu.Game
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add);
loadComponentSingleFile(musicController = new MusicController
{
- Depth = -4,
+ Depth = -5,
Position = new Vector2(0, Toolbar.HEIGHT),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs
index ea077ff645..c106446fe0 100644
--- a/osu.Game/Overlays/UserProfileOverlay.cs
+++ b/osu.Game/Overlays/UserProfileOverlay.cs
@@ -77,9 +77,11 @@ namespace osu.Game.Overlays
public void ShowUser(User user, bool fetchOnline = true)
{
+ if (user == User.SYSTEM_USER) return;
+
Show();
- if (user.Id == Header?.User.Id)
+ if (user.Id == Header?.User?.Id)
return;
userReq?.Cancel();
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 7e3e955740..a274d9b12f 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -7,6 +7,8 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.TypeExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@@ -165,6 +167,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
}
+ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ {
+ if (!AllJudged)
+ return false;
+
+ return base.UpdateSubTreeMasking(source, maskingBounds);
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index f0f765a4c9..5ad6427fd8 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -84,11 +84,10 @@ namespace osu.Game.Screens.Menu
private const double early_activation = 60;
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
public OsuLogo()
{
- // Required to make Schedule calls run in OsuScreen even when we are not visible.
- AlwaysPresent = true;
-
EarlyActivationMilliseconds = early_activation;
Size = new Vector2(default_size);
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index f51ea6fe3e..eb137f5447 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- KeyCounter = CreateKeyCounter(),
+ KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock),
HoldToQuit = CreateQuitButton(),
}
}
@@ -194,12 +194,13 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20 }
};
- protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection
+ protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection
{
FadeTime = 50,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
+ AudioClock = offsetClock
};
protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6)
diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs
index 2c31e61114..d1efe4cab7 100644
--- a/osu.Game/Screens/Play/KeyCounter.cs
+++ b/osu.Game/Screens/Play/KeyCounter.cs
@@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -19,6 +21,9 @@ namespace osu.Game.Screens.Play
private Container textLayer;
private SpriteText countSpriteText;
+ private readonly List states = new List();
+ private KeyCounterState currentState;
+
public bool IsCounting { get; set; } = true;
private int countPresses;
public int CountPresses
@@ -45,7 +50,10 @@ namespace osu.Game.Screens.Play
isLit = value;
updateGlowSprite(value);
if (value && IsCounting)
+ {
CountPresses++;
+ saveState();
+ }
}
}
}
@@ -128,6 +136,32 @@ namespace osu.Game.Screens.Play
}
}
- public void ResetCount() => CountPresses = 0;
+ public void ResetCount()
+ {
+ CountPresses = 0;
+ states.Clear();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentState?.Time > Clock.CurrentTime)
+ restoreStateTo(Clock.CurrentTime);
+ }
+
+ private void saveState()
+ {
+ if (currentState == null || currentState.Time < Clock.CurrentTime)
+ states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses));
+ }
+
+ private void restoreStateTo(double time)
+ {
+ states.RemoveAll(state => state.Time > time);
+
+ currentState = states.LastOrDefault();
+ CountPresses = currentState?.Count ?? 0;
+ }
}
}
diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs
index 5cb5bb152a..2a737d974b 100644
--- a/osu.Game/Screens/Play/KeyCounterCollection.cs
+++ b/osu.Game/Screens/Play/KeyCounterCollection.cs
@@ -3,15 +3,16 @@
using System;
using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using OpenTK.Graphics;
-using osu.Framework.Configuration;
-using osu.Framework.Allocation;
using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States;
+using osu.Framework.Timing;
using osu.Game.Configuration;
using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Screens.Play
{
@@ -37,6 +38,9 @@ namespace osu.Game.Screens.Play
key.FadeTime = FadeTime;
key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor;
+ // Use the same clock object as SongProgress for saving KeyCounter state
+ if (AudioClock != null)
+ key.Clock = AudioClock;
}
public void ResetCount()
@@ -118,6 +122,8 @@ namespace osu.Game.Screens.Play
public override bool HandleKeyboardInput => receptor == null;
public override bool HandleMouseInput => receptor == null;
+ public IFrameBasedClock AudioClock { get; set; }
+
private Receptor receptor;
public Receptor GetReceptor()
diff --git a/osu.Game/Screens/Play/KeyCounterState.cs b/osu.Game/Screens/Play/KeyCounterState.cs
new file mode 100644
index 0000000000..e5c0703319
--- /dev/null
+++ b/osu.Game/Screens/Play/KeyCounterState.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Screens.Play
+{
+ public class KeyCounterState
+ {
+ public KeyCounterState(double time, int count)
+ {
+ Time = time;
+ Count = count;
+ }
+
+ public readonly double Time;
+ public readonly int Count;
+ }
+}
diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs
index 7de0756dbd..8a032064d3 100644
--- a/osu.Game/Storyboards/CommandTimeline.cs
+++ b/osu.Game/Storyboards/CommandTimeline.cs
@@ -16,10 +16,10 @@ namespace osu.Game.Storyboards
public bool HasCommands => commands.Count > 0;
private Cached startTimeBacking;
- public double StartTime => startTimeBacking.IsValid ? startTimeBacking : (startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue);
+ public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue;
private Cached endTimeBacking;
- public double EndTime => endTimeBacking.IsValid ? endTimeBacking : (endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue);
+ public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue;
public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default(T);
public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default(T);
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index f42df4023f..6c3d2bfa63 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -144,5 +144,14 @@ namespace osu.Game.Users
public Badge[] Badges;
public override string ToString() => Username;
+
+ ///
+ /// A user instance for displaying locally created system messages.
+ ///
+ public static readonly User SYSTEM_USER = new User
+ {
+ Username = "system",
+ Id = 0
+ };
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 83ab5534c6..05291cf3d0 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/osu.TestProject.props b/osu.TestProject.props
index 58de6ec030..506d634555 100644
--- a/osu.TestProject.props
+++ b/osu.TestProject.props
@@ -11,7 +11,7 @@
-
+