diff --git a/osu.Android.props b/osu.Android.props
index 6a8e66ee6a..66a1523843 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index fbbe00bb6c..fe0d512166 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[BackgroundDependencyLoader]
private void load()
{
- SetContents(() => new CatcherArea.Catcher
+ SetContents(() => new Catcher
{
RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 070847c0c1..df5494aab0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(CatcherArea.Catcher),
+ typeof(Catcher),
typeof(DrawableCatchRuleset),
typeof(DrawableFruit),
typeof(DrawableJuiceStream),
@@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableCatchRuleset drawableRuleset;
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public void Setup() => Schedule(() =>
{
var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint());
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Tests
ControlPointInfo = controlPointInfo
});
- Add(new Container
+ Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -66,16 +66,49 @@ namespace osu.Game.Rulesets.Catch.Tests
{
drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo))
}
- });
+ };
+ });
+
+ [Test]
+ public void TestFruits()
+ {
+ AddStep("hit fruits", () => spawnFruits(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
AddStep("miss fruits", () => spawnFruits());
- AddStep("hit fruits", () => spawnFruits(true));
- AddStep("miss juicestream", () => spawnJuiceStream());
- AddStep("hit juicestream", () => spawnJuiceStream(true));
- AddStep("miss bananas", () => spawnBananas());
- AddStep("hit bananas", () => spawnBananas(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is failed", () => catcherState == CatcherAnimationState.Fail);
}
+ [Test]
+ public void TestJuicestream()
+ {
+ AddStep("hit juicestream", () => spawnJuiceStream(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+
+ AddStep("miss juicestream", () => spawnJuiceStream());
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is failed", () => catcherState == CatcherAnimationState.Fail);
+ }
+
+ [Test]
+ public void TestBananas()
+ {
+ AddStep("hit bananas", () => spawnBananas(true));
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+
+ AddStep("miss bananas", () => spawnBananas());
+ AddUntilStep("wait for completion", () => playfieldIsEmpty);
+ AddAssert("catcher state is idle", () => catcherState == CatcherAnimationState.Idle);
+ }
+
+ private bool playfieldIsEmpty => !((CatchPlayfield)drawableRuleset.Playfield).AllHitObjects.Any(h => h.IsAlive);
+
+ private CatcherAnimationState catcherState => ((CatchPlayfield)drawableRuleset.Playfield).CatcherArea.MovableCatcher.CurrentState;
+
private void spawnFruits(bool hit = false)
{
for (int i = 1; i <= 4; i++)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 6f0d8f0a3a..49ff9df4d7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
}
}
- private CatcherArea.Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher;
+ private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 1a5d0f983b..7c81bcdf0c 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
ApplyPositionOffsets(Beatmap);
- initialiseHyperDash((List)Beatmap.HitObjects);
-
int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType())
@@ -76,6 +74,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
break;
case JuiceStream juiceStream:
+ // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
+ lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH;
+
+ // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
+ lastStartTime = juiceStream.StartTime;
+
foreach (var nested in juiceStream.NestedHitObjects)
{
var catchObject = (CatchHitObject)nested;
@@ -90,20 +94,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
break;
}
}
+
+ initialiseHyperDash(beatmap);
}
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
{
- if (hitObject is JuiceStream stream)
- {
- lastPosition = stream.EndX;
- lastStartTime = stream.EndTime;
- return;
- }
-
- if (!(hitObject is Fruit))
- return;
-
float offsetPosition = hitObject.X;
double startTime = hitObject.StartTime;
@@ -116,7 +112,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
float positionDiff = offsetPosition - lastPosition.Value;
- double timeDiff = startTime - lastStartTime;
+
+ // Todo: BUG!! Stable calculated time deltas as ints, which affects randomisation. This should be changed to a double.
+ int timeDiff = (int)(startTime - lastStartTime);
if (timeDiff > 1000)
{
@@ -132,7 +130,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
return;
}
- if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ // ReSharper disable once PossibleLossOfFraction
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff);
hitObject.XOffset = offsetPosition - hitObject.X;
@@ -191,14 +190,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- private void initialiseHyperDash(List objects)
+ private static void initialiseHyperDash(IBeatmap beatmap)
{
List objectWithDroplets = new List();
- foreach (var currentObject in objects)
+ foreach (var currentObject in beatmap.HitObjects)
{
- if (currentObject is Fruit)
- objectWithDroplets.Add(currentObject);
+ if (currentObject is Fruit fruitObject)
+ objectWithDroplets.Add(fruitObject);
if (currentObject is JuiceStream)
{
@@ -212,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
- double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
+ double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
@@ -221,10 +220,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject currentObject = objectWithDroplets[i];
CatchHitObject nextObject = objectWithDroplets[i + 1];
+ // Reset variables in-case values have changed (e.g. after applying HR)
+ currentObject.HyperDashTarget = null;
+ currentObject.DistanceToHyperDash = 0;
+
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
- float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
+ float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
{
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 44e1a8e5cc..5880a227c2 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override int SectionLength => 750;
+ private float halfCatcherWidth;
+
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -48,14 +50,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
- float halfCatchWidth;
-
- using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty))
- {
- halfCatchWidth = catcher.CatchWidth * 0.5f;
- halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
- }
-
CatchHitObject lastObject = null;
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
@@ -69,16 +63,25 @@ namespace osu.Game.Rulesets.Catch.Difficulty
continue;
if (lastObject != null)
- yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth);
+ yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatcherWidth);
lastObject = hitObject;
}
}
- protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap)
{
- new Movement(),
- };
+ using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
+ {
+ halfCatcherWidth = catcher.CatchWidth * 0.5f;
+ halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
+ }
+
+ return new Skill[]
+ {
+ new Movement(halfCatcherWidth),
+ };
+ }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index 7cd569035b..fd164907e0 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -20,9 +20,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
protected override double DecayWeight => 0.94;
+ protected readonly float HalfCatcherWidth;
+
private float? lastPlayerPosition;
private float lastDistanceMoved;
+ public Movement(float halfCatcherWidth)
+ {
+ HalfCatcherWidth = halfCatcherWidth;
+ }
+
protected override double StrainValueOf(DifficultyHitObject current)
{
var catchCurrent = (CatchDifficultyHitObject)current;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 4c72b9fd3e..1ef235f764 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
{
- private readonly CatcherArea.Catcher catcher;
+ private readonly Catcher catcher;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 4649dcae90..b90b5812a6 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Replays
public override Replay Generate()
{
// todo: add support for HT DT
- const double dash_speed = CatcherArea.Catcher.BASE_SPEED;
+ const double dash_speed = Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2;
float lastPosition = 0.5f;
double lastTime = 0;
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
new file mode 100644
index 0000000000..e361b29a9d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -0,0 +1,458 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class Catcher : Container, IKeyBindingHandler
+ {
+ ///
+ /// Whether we are hyper-dashing or not.
+ ///
+ public bool HyperDashing => hyperDashModifier != 1;
+
+ ///
+ /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
+ ///
+ public const double BASE_SPEED = 1.0 / 512;
+
+ public Container ExplodingFruitTarget;
+
+ public Container AdditiveTarget;
+
+ public CatcherAnimationState CurrentState { get; private set; }
+
+ ///
+ /// Width of the area that can be used to attempt catches during gameplay.
+ ///
+ internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
+
+ protected bool Dashing
+ {
+ get => dashing;
+ set
+ {
+ if (value == dashing) return;
+
+ dashing = value;
+
+ Trail |= dashing;
+ }
+ }
+
+ ///
+ /// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
+ ///
+ protected bool Trail
+ {
+ get => trail;
+ set
+ {
+ if (value == trail || AdditiveTarget == null) return;
+
+ trail = value;
+
+ if (Trail)
+ beginTrail();
+ }
+ }
+
+ private Container caughtFruit;
+
+ private CatcherSprite catcherIdle;
+ private CatcherSprite catcherKiai;
+ private CatcherSprite catcherFail;
+
+ private CatcherSprite currentCatcher;
+
+ private int currentDirection;
+
+ private bool dashing;
+
+ private bool trail;
+
+ private double hyperDashModifier = 1;
+ private int hyperDashDirection;
+ private float hyperDashTargetPosition;
+
+ public Catcher(BeatmapDifficulty difficulty = null)
+ {
+ RelativePositionAxes = Axes.X;
+ X = 0.5f;
+
+ Origin = Anchor.TopCentre;
+
+ Size = new Vector2(CatcherArea.CATCHER_SIZE);
+ if (difficulty != null)
+ Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ caughtFruit = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ },
+ catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ },
+ catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ },
+ catcherFail = new CatcherSprite(CatcherAnimationState.Fail)
+ {
+ Anchor = Anchor.TopCentre,
+ Alpha = 0,
+ }
+ };
+
+ updateCatcher();
+ }
+
+ ///
+ /// Add a caught fruit to the catcher's stack.
+ ///
+ /// The fruit that was caught.
+ public void PlaceOnPlate(DrawableCatchHitObject fruit)
+ {
+ var ourRadius = fruit.DisplayRadius;
+ float theirRadius = 0;
+
+ const float allowance = 6;
+
+ while (caughtFruit.Any(f =>
+ f.LifetimeEnd == double.MaxValue &&
+ Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
+ {
+ var diff = (ourRadius + theirRadius) / allowance;
+ fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
+ fruit.Y -= RNG.NextSingle() * diff;
+ }
+
+ fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
+
+ caughtFruit.Add(fruit);
+
+ Add(new HitExplosion(fruit)
+ {
+ X = fruit.X,
+ Scale = new Vector2(fruit.HitObject.Scale)
+ });
+ }
+
+ ///
+ /// Let the catcher attempt to catch a fruit.
+ ///
+ /// The fruit to catch.
+ /// Whether the catch is possible.
+ public bool AttemptCatch(CatchHitObject fruit)
+ {
+ var halfCatchWidth = CatchWidth * 0.5f;
+
+ // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
+ var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
+ var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
+
+ var validCatch =
+ catchObjectPosition >= catcherPosition - halfCatchWidth &&
+ catchObjectPosition <= catcherPosition + halfCatchWidth;
+
+ // only update hyperdash state if we are catching a fruit.
+ // exceptions are Droplets and JuiceStreams.
+ if (!(fruit is Fruit)) return validCatch;
+
+ if (validCatch && fruit.HyperDash)
+ {
+ var target = fruit.HyperDashTarget;
+ var timeDifference = target.StartTime - fruit.StartTime;
+ double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
+ var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
+
+ SetHyperDashState(Math.Abs(velocity), target.X);
+ }
+ else
+ SetHyperDashState();
+
+ if (validCatch)
+ updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
+ else if (!(fruit is Banana))
+ updateState(CatcherAnimationState.Fail);
+
+ return validCatch;
+ }
+
+ ///
+ /// Set hyper-dash state.
+ ///
+ /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.
+ /// When this catcher crosses this position, this catcher ends hyper-dashing.
+ public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
+ {
+ const float hyper_dash_transition_length = 180;
+
+ var wasHyperDashing = HyperDashing;
+
+ if (modifier <= 1 || X == targetPosition)
+ {
+ hyperDashModifier = 1;
+ hyperDashDirection = 0;
+
+ if (wasHyperDashing)
+ {
+ this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
+ Trail &= Dashing;
+ }
+ }
+ else
+ {
+ hyperDashModifier = modifier;
+ hyperDashDirection = Math.Sign(targetPosition - X);
+ hyperDashTargetPosition = targetPosition;
+
+ if (!wasHyperDashing)
+ {
+ this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
+ this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
+ Trail = true;
+
+ var hyperDashEndGlow = createAdditiveSprite();
+
+ hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
+ hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
+ hyperDashEndGlow.FadeOut(1200);
+ hyperDashEndGlow.Expire(true);
+ }
+ }
+ }
+
+ public bool OnPressed(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection--;
+ return true;
+
+ case CatchAction.MoveRight:
+ currentDirection++;
+ return true;
+
+ case CatchAction.Dash:
+ Dashing = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection++;
+ break;
+
+ case CatchAction.MoveRight:
+ currentDirection--;
+ break;
+
+ case CatchAction.Dash:
+ Dashing = false;
+ break;
+ }
+ }
+
+ public void UpdatePosition(float position)
+ {
+ position = Math.Clamp(position, 0, 1);
+
+ if (position == X)
+ return;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
+ X = position;
+ }
+
+ ///
+ /// Drop any fruit off the plate.
+ ///
+ public void Drop()
+ {
+ foreach (var f in caughtFruit.ToArray())
+ Drop(f);
+ }
+
+ ///
+ /// Explode any fruit off the plate.
+ ///
+ public void Explode()
+ {
+ foreach (var f in caughtFruit.ToArray())
+ Explode(f);
+ }
+
+ public void Drop(DrawableHitObject fruit)
+ {
+ removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y + 75, 750, Easing.InSine);
+ f.FadeOut(750);
+ });
+ }
+
+ public void Explode(DrawableHitObject fruit)
+ {
+ var originalX = fruit.X * Scale.X;
+
+ removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
+ f.MoveToX(f.X + originalX * 6, 1000);
+ f.FadeOut(750);
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentDirection == 0) return;
+
+ var direction = Math.Sign(currentDirection);
+
+ var dashModifier = Dashing ? 1 : 0.5;
+ var speed = BASE_SPEED * dashModifier * hyperDashModifier;
+
+ UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
+
+ // Correct overshooting.
+ if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
+ (hyperDashDirection < 0 && hyperDashTargetPosition > X))
+ {
+ X = hyperDashTargetPosition;
+ SetHyperDashState();
+ }
+ }
+
+ private void updateCatcher()
+ {
+ currentCatcher?.Hide();
+
+ switch (CurrentState)
+ {
+ default:
+ currentCatcher = catcherIdle;
+ break;
+
+ case CatcherAnimationState.Fail:
+ currentCatcher = catcherFail;
+ break;
+
+ case CatcherAnimationState.Kiai:
+ currentCatcher = catcherKiai;
+ break;
+ }
+
+ currentCatcher.Show();
+ (currentCatcher.Drawable as IAnimation)?.GotoFrame(0);
+ }
+
+ private void beginTrail()
+ {
+ if (!dashing && !HyperDashing)
+ {
+ Trail = false;
+ return;
+ }
+
+ var additive = createAdditiveSprite();
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
+ additive.Expire(true);
+
+ Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
+ }
+
+ private void updateState(CatcherAnimationState state)
+ {
+ if (CurrentState == state)
+ return;
+
+ CurrentState = state;
+ updateCatcher();
+ }
+
+ private CatcherTrailSprite createAdditiveSprite()
+ {
+ var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
+
+ var sprite = new CatcherTrailSprite(tex)
+ {
+ Anchor = Anchor,
+ Scale = Scale,
+ Colour = HyperDashing ? Color4.Red : Color4.White,
+ Blending = BlendingParameters.Additive,
+ RelativePositionAxes = RelativePositionAxes,
+ Position = Position
+ };
+
+ AdditiveTarget?.Add(sprite);
+
+ return sprite;
+ }
+
+ private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
+ {
+ if (ExplodingFruitTarget != null)
+ {
+ fruit.Anchor = Anchor.TopLeft;
+ fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
+
+ if (!caughtFruit.Remove(fruit))
+ // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
+ // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
+ return;
+
+ ExplodingFruitTarget.Add(fruit);
+ }
+
+ var actionTime = Clock.CurrentTime;
+
+ fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
+ onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
+
+ void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
+ {
+ using (fruit.BeginAbsoluteSequence(actionTime))
+ action(fruit);
+
+ fruit.Expire();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 9ee94636f1..e0d9ff759d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -2,15 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
@@ -20,7 +13,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -28,8 +20,6 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float CATCHER_SIZE = 106.75f;
- protected internal readonly Catcher MovableCatcher;
-
public Func> CreateDrawableRepresentation;
public Container ExplodingFruitTarget
@@ -37,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.UI
set => MovableCatcher.ExplodingFruitTarget = value;
}
+ private DrawableCatchHitObject lastPlateableFruit;
+
public CatcherArea(BeatmapDifficulty difficulty = null)
{
RelativeSizeAxes = Axes.X;
@@ -47,7 +39,10 @@ namespace osu.Game.Rulesets.Catch.UI
};
}
- private DrawableCatchHitObject lastPlateableFruit;
+ public static float GetCatcherSize(BeatmapDifficulty difficulty)
+ {
+ return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
{
@@ -100,6 +95,15 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
+ public void OnReleased(CatchAction action)
+ {
+ }
+
+ public bool AttemptCatch(CatchHitObject obj)
+ {
+ return MovableCatcher.AttemptCatch(obj);
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -110,559 +114,6 @@ namespace osu.Game.Rulesets.Catch.UI
MovableCatcher.X = state.CatcherX.Value;
}
- public void OnReleased(CatchAction action)
- {
- }
-
- 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
- {
- ///
- /// Width of the area that can be used to attempt catches during gameplay.
- ///
- internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
-
- private Container caughtFruit;
-
- public Container ExplodingFruitTarget;
-
- public Container AdditiveTarget;
-
- public Catcher(BeatmapDifficulty difficulty = null)
- {
- RelativePositionAxes = Axes.X;
- X = 0.5f;
-
- Origin = Anchor.TopCentre;
-
- Size = new Vector2(CATCHER_SIZE);
- if (difficulty != null)
- Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Children = new Drawable[]
- {
- caughtFruit = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
- },
- catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
- {
- Anchor = Anchor.TopCentre,
- Alpha = 0,
- },
- catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai)
- {
- Anchor = Anchor.TopCentre,
- Alpha = 0,
- },
- catcherFail = new CatcherSprite(CatcherAnimationState.Fail)
- {
- Anchor = Anchor.TopCentre,
- Alpha = 0,
- }
- };
-
- updateCatcher();
- }
-
- private CatcherSprite catcherIdle;
- private CatcherSprite catcherKiai;
- private CatcherSprite catcherFail;
-
- private void updateCatcher()
- {
- catcherIdle.Hide();
- catcherKiai.Hide();
- catcherFail.Hide();
-
- CatcherSprite current;
-
- switch (currentState)
- {
- default:
- current = catcherIdle;
- break;
-
- case CatcherAnimationState.Fail:
- current = catcherFail;
- break;
-
- case CatcherAnimationState.Kiai:
- current = catcherKiai;
- break;
- }
-
- current.Show();
- (current.Drawable as IAnimation)?.GotoFrame(0);
- }
-
- private int currentDirection;
-
- private bool dashing;
-
- protected bool Dashing
- {
- get => dashing;
- set
- {
- if (value == dashing) return;
-
- dashing = value;
-
- Trail |= dashing;
- }
- }
-
- private bool trail;
-
- ///
- /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
- ///
- protected bool Trail
- {
- get => trail;
- set
- {
- if (value == trail) return;
-
- trail = value;
-
- if (Trail)
- beginTrail();
- }
- }
-
- private void beginTrail()
- {
- Trail &= dashing || HyperDashing;
- Trail &= AdditiveTarget != null;
-
- if (!Trail) return;
-
- var additive = createAdditiveSprite(HyperDashing);
-
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
- additive.Expire(true);
-
- Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
- }
-
- private Drawable createAdditiveSprite(bool hyperDash)
- {
- var additive = createCatcherSprite();
-
- additive.Anchor = Anchor;
- additive.Scale = Scale;
- additive.Colour = hyperDash ? Color4.Red : Color4.White;
- additive.Blending = BlendingParameters.Additive;
- additive.RelativePositionAxes = RelativePositionAxes;
- additive.Position = Position;
-
- AdditiveTarget.Add(additive);
-
- return additive;
- }
-
- private Drawable createCatcherSprite() => new CatcherSprite(currentState);
-
- ///
- /// Add a caught fruit to the catcher's stack.
- ///
- /// The fruit that was caught.
- public void PlaceOnPlate(DrawableCatchHitObject fruit)
- {
- float ourRadius = fruit.DisplayRadius;
- float theirRadius = 0;
-
- const float allowance = 6;
-
- while (caughtFruit.Any(f =>
- f.LifetimeEnd == double.MaxValue &&
- Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
- {
- float diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
- fruit.Y -= RNG.NextSingle() * diff;
- }
-
- fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
-
- caughtFruit.Add(fruit);
-
- Add(new HitExplosion(fruit)
- {
- X = fruit.X,
- Scale = new Vector2(fruit.HitObject.Scale)
- });
- }
-
- ///
- /// Let the catcher attempt to catch a fruit.
- ///
- /// The fruit to catch.
- /// Whether the catch is possible.
- public bool AttemptCatch(CatchHitObject fruit)
- {
- float halfCatchWidth = CatchWidth * 0.5f;
-
- // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
- var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
- var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
-
- var validCatch =
- catchObjectPosition >= catcherPosition - halfCatchWidth &&
- catchObjectPosition <= catcherPosition + halfCatchWidth;
-
- // only update hyperdash state if we are catching a fruit.
- // exceptions are Droplets and JuiceStreams.
- if (!(fruit is Fruit)) return validCatch;
-
- if (validCatch && fruit.HyperDash)
- {
- var target = fruit.HyperDashTarget;
- double timeDifference = target.StartTime - fruit.StartTime;
- double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
- double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
-
- SetHyperDashState(Math.Abs(velocity), target.X);
- }
- else
- {
- SetHyperDashState();
- }
-
- if (validCatch)
- updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
- else
- updateState(CatcherAnimationState.Fail);
-
- return validCatch;
- }
-
- private void updateState(CatcherAnimationState state)
- {
- if (currentState == state)
- return;
-
- currentState = state;
- updateCatcher();
- }
-
- private CatcherAnimationState currentState;
-
- private double hyperDashModifier = 1;
- private int hyperDashDirection;
- private float hyperDashTargetPosition;
-
- ///
- /// Whether we are hyper-dashing or not.
- ///
- public bool HyperDashing => hyperDashModifier != 1;
-
- ///
- /// Set hyper-dash state.
- ///
- /// The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.
- /// When this catcher crosses this position, this catcher ends hyper-dashing.
- public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
- {
- const float hyper_dash_transition_length = 180;
-
- bool wasHyperDashing = HyperDashing;
-
- if (modifier <= 1 || X == targetPosition)
- {
- hyperDashModifier = 1;
- hyperDashDirection = 0;
-
- if (wasHyperDashing)
- {
- this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
- Trail &= Dashing;
- }
- }
- else
- {
- hyperDashModifier = modifier;
- hyperDashDirection = Math.Sign(targetPosition - X);
- hyperDashTargetPosition = targetPosition;
-
- if (!wasHyperDashing)
- {
- this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
- this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
- Trail = true;
-
- var hyperDashEndGlow = createAdditiveSprite(true);
-
- hyperDashEndGlow.MoveToOffset(new Vector2(0, -20), 1200, Easing.In);
- hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.9f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
- hyperDashEndGlow.FadeOut(1200);
- hyperDashEndGlow.Expire(true);
- }
- }
- }
-
- public bool OnPressed(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection--;
- return true;
-
- case CatchAction.MoveRight:
- currentDirection++;
- return true;
-
- case CatchAction.Dash:
- Dashing = true;
- return true;
- }
-
- return false;
- }
-
- public void OnReleased(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection++;
- break;
-
- case CatchAction.MoveRight:
- currentDirection--;
- break;
-
- case CatchAction.Dash:
- Dashing = false;
- break;
- }
- }
-
- ///
- /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
- ///
- public const double BASE_SPEED = 1.0 / 512;
-
- protected override void Update()
- {
- base.Update();
-
- if (currentDirection == 0) return;
-
- var direction = Math.Sign(currentDirection);
-
- double dashModifier = Dashing ? 1 : 0.5;
- double speed = BASE_SPEED * dashModifier * hyperDashModifier;
-
- UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
-
- // Correct overshooting.
- if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
- (hyperDashDirection < 0 && hyperDashTargetPosition > X))
- {
- X = hyperDashTargetPosition;
- SetHyperDashState();
- }
- }
-
- public void UpdatePosition(float position)
- {
- position = Math.Clamp(position, 0, 1);
-
- if (position == X)
- return;
-
- Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
- X = position;
- }
-
- ///
- /// Drop any fruit off the plate.
- ///
- public void Drop()
- {
- foreach (var f in caughtFruit.ToArray())
- Drop(f);
- }
-
- ///
- /// Explode any fruit off the plate.
- ///
- public void Explode()
- {
- foreach (var f in caughtFruit.ToArray())
- Explode(f);
- }
-
- public void Drop(DrawableHitObject fruit) => removeFromPlateWithTransform(fruit, f =>
- {
- f.MoveToY(f.Y + 75, 750, Easing.InSine);
- f.FadeOut(750);
- });
-
- public void Explode(DrawableHitObject fruit)
- {
- var originalX = fruit.X * Scale.X;
-
- removeFromPlateWithTransform(fruit, f =>
- {
- f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
- f.MoveToX(f.X + originalX * 6, 1000);
- f.FadeOut(750);
- });
- }
-
- private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
- {
- if (ExplodingFruitTarget != null)
- {
- fruit.Anchor = Anchor.TopLeft;
- fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
-
- if (!caughtFruit.Remove(fruit))
- // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
- // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
- return;
-
- ExplodingFruitTarget.Add(fruit);
- }
-
- double actionTime = Clock.CurrentTime;
-
- fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
- onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
-
- void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
- {
- using (fruit.BeginAbsoluteSequence(actionTime))
- action(fruit);
-
- fruit.Expire();
- }
- }
- }
- }
-
- public class HitExplosion : CompositeDrawable
- {
- private readonly CircularContainer largeFaint;
-
- public HitExplosion(DrawableCatchHitObject fruit)
- {
- Size = new Vector2(20);
- Anchor = Anchor.TopCentre;
- Origin = Anchor.BottomCentre;
-
- Color4 objectColour = fruit.AccentColour.Value;
-
- // scale roughly in-line with visual appearance of notes
-
- const float angle_variangle = 15; // should be less than 45
-
- const float roundness = 100;
-
- const float initial_height = 10;
-
- var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
-
- InternalChildren = new Drawable[]
- {
- largeFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.8f),
- Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
- },
- new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
- }
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- const double duration = 400;
-
- largeFaint
- .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
- .FadeOut(duration * 2);
-
- this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
- Expire(true);
- }
+ protected internal readonly Catcher MovableCatcher;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
new file mode 100644
index 0000000000..56cb7dbfda
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherTrailSprite : Sprite
+ {
+ public CatcherTrailSprite(Texture texture)
+ {
+ Texture = texture;
+
+ Size = new Vector2(CatcherArea.CATCHER_SIZE);
+
+ // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
+ OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
new file mode 100644
index 0000000000..04a86f83be
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -0,0 +1,122 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class HitExplosion : CompositeDrawable
+ {
+ private readonly CircularContainer largeFaint;
+
+ public HitExplosion(DrawableCatchHitObject fruit)
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.BottomCentre;
+
+ Color4 objectColour = fruit.AccentColour.Value;
+
+ // scale roughly in-line with visual appearance of notes
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 100;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ // we want our size to be very small so the glow dominates it.
+ Size = new Vector2(0.8f),
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const double duration = 400;
+
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ Expire(true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index c871089acd..5a6dd49c44 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -16,16 +16,24 @@ using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
+using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
- public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
+ public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
{
public Func GetInitialHitAction;
+ public Color4 AccentColour
+ {
+ get => ball.Colour;
+ set => ball.Colour = value;
+ }
+
private readonly Slider slider;
private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider;
+ private readonly CircularContainer ball;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
@@ -47,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Alpha = 0,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
- new CircularContainer
+ ball = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
index c76d4fd5b8..7a257a1603 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
@@ -78,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount);
AddAssert($"msg #{index} has the right action", hasExpectedActions);
- AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
+ //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks);
bool hasExpectedActions()
@@ -97,7 +96,7 @@ namespace osu.Game.Tests.Visual.Online
return true;
}
- bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics);
+ //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics);
bool isShowingLinks()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 19bdaff6ff..736bfd8e7d 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -38,8 +38,13 @@ namespace osu.Game.Tests.Visual.Online
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
+ private IEnumerable visibleChannels => chatOverlay.ChannelTabControl.VisibleItems.Where(channel => channel.Name != "+");
+ private IEnumerable joinedChannels => chatOverlay.ChannelTabControl.Items.Where(channel => channel.Name != "+");
private readonly List channels;
+ private Channel currentChannel => channelManager.CurrentChannel.Value;
+ private Channel nextChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) + 1);
+ private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1);
private Channel channel1 => channels[0];
private Channel channel2 => channels[1];
@@ -91,7 +96,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
- AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
+ AddAssert("Current channel is channel 1", () => currentChannel == channel1);
AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
@@ -102,12 +107,12 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Join channel 2", () => channelManager.JoinChannel(channel2));
AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
- AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
+ AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
- AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
+ AddAssert("Current channel is channel 1", () => currentChannel == channel1);
- AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
+ AddStep("Close channel 1", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
}
@@ -140,10 +145,67 @@ namespace osu.Game.Tests.Visual.Online
var targetNumberKey = oneBasedIndex % 10;
var targetChannel = channels[zeroBasedIndex];
AddStep($"press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey));
- AddAssert($"channel #{oneBasedIndex} is selected", () => channelManager.CurrentChannel.Value == targetChannel);
+ AddAssert($"channel #{oneBasedIndex} is selected", () => currentChannel == targetChannel);
}
}
+ private Channel expectedChannel;
+
+ [Test]
+ public void TestCloseChannelWhileActive()
+ {
+ AddUntilStep("Join until dropdown has channels", () =>
+ {
+ if (visibleChannels.Count() < joinedChannels.Count())
+ return true;
+
+ // Using temporary channels because they don't hide their names when not active
+ Channel toAdd = new Channel { Name = $"test channel {joinedChannels.Count()}", Type = ChannelType.Temporary };
+ channelManager.JoinChannel(toAdd);
+
+ return false;
+ });
+
+ AddStep("Switch to last tab", () => clickDrawable(chatOverlay.TabMap[visibleChannels.Last()]));
+ AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last());
+
+ // Closing the last channel before dropdown
+ AddStep("Close current channel", () =>
+ {
+ expectedChannel = nextChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Next channel selected", () => currentChannel == expectedChannel);
+
+ // Depending on the window size, one more channel might need to be closed for the selectorTab to appear
+ AddUntilStep("Close channels until selector visible", () =>
+ {
+ if (chatOverlay.ChannelTabControl.VisibleItems.Last().Name == "+")
+ return true;
+
+ chatOverlay.ChannelTabControl.RemoveChannel(visibleChannels.Last());
+ return false;
+ });
+ AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last());
+
+ // Closing the last channel with dropdown no longer present
+ AddStep("Close last when selector next", () =>
+ {
+ expectedChannel = previousChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Channel changed to previous", () => currentChannel == expectedChannel);
+
+ // Standard channel closing
+ AddStep("Switch to previous channel", () => chatOverlay.ChannelTabControl.SwitchTab(-1));
+ AddStep("Close current channel", () =>
+ {
+ expectedChannel = nextChannel;
+ chatOverlay.ChannelTabControl.RemoveChannel(currentChannel);
+ });
+ AddAssert("Channel changed to next", () => currentChannel == expectedChannel);
+ }
+
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
@@ -187,6 +249,8 @@ namespace osu.Game.Tests.Visual.Online
{
public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
+ public new ChannelTabControl ChannelTabControl => base.ChannelTabControl;
+
public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay;
protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl();
@@ -196,12 +260,22 @@ namespace osu.Game.Tests.Visual.Online
private class TestTabControl : ChannelTabControl
{
- protected override TabItem CreateTabItem(Channel value) => new TestChannelTabItem(value);
+ protected override TabItem CreateTabItem(Channel value)
+ {
+ switch (value.Type)
+ {
+ case ChannelType.PM:
+ return new TestPrivateChannelTabItem(value);
+
+ default:
+ return new TestChannelTabItem(value);
+ }
+ }
public new IReadOnlyDictionary> TabMap => base.TabMap;
}
- private class TestChannelTabItem : PrivateChannelTabItem
+ private class TestChannelTabItem : ChannelTabItem
{
public TestChannelTabItem(Channel channel)
: base(channel)
@@ -210,5 +284,15 @@ namespace osu.Game.Tests.Visual.Online
public new ClickableContainer CloseButton => base.CloseButton;
}
+
+ private class TestPrivateChannelTabItem : PrivateChannelTabItem
+ {
+ public TestPrivateChannelTabItem(Channel channel)
+ : base(channel)
+ {
+ }
+
+ public new ClickableContainer CloseButton => base.CloseButton;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index d80add3015..8df75c78f5 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -497,7 +497,7 @@ namespace osu.Game.Tests.Visual.SongSelect
}
bool changed = false;
- AddStep($"Load {beatmapSets.Count} Beatmaps", () =>
+ AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () =>
{
carousel.Filter(new FilterCriteria());
carousel.BeatmapSetsChanged = () => changed = true;
@@ -697,6 +697,8 @@ namespace osu.Game.Tests.Visual.SongSelect
public new List Items => base.Items;
public bool PendingFilterTask => PendingFilter != null;
+
+ protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty();
}
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 9f33d03ac4..4405c75744 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -643,6 +643,10 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
+ int previousSetID = 0;
+
+ AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID);
+
AddStep("Click on a difficulty", () =>
{
InputManager.MoveMouseTo(difficultyIcon);
@@ -652,6 +656,9 @@ namespace osu.Game.Tests.Visual.SongSelect
});
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
+
+ AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmap.BeatmapSet.ID == previousSetID);
+ AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
}
[Test]
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs
new file mode 100644
index 0000000000..9f885ed827
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchHeader.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Screens.Gameplay.Components;
+using osuTK;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneMatchHeader : TournamentTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableTournamentHeaderText),
+ typeof(DrawableTournamentHeaderLogo),
+ };
+
+ public TestSceneMatchHeader()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(50),
+ Children = new Drawable[]
+ {
+ new TournamentSpriteText { Text = "with logo", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader(),
+ new TournamentSpriteText { Text = "without logo", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader { ShowLogo = false },
+ new TournamentSpriteText { Text = "without scores", Font = OsuFont.Torus.With(size: 30) },
+ new MatchHeader { ShowScores = false },
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs
index fa5c941f1a..ef8c8767e0 100644
--- a/osu.Game.Tournament/Components/ControlPanel.cs
+++ b/osu.Game.Tournament/Components/ControlPanel.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Tournament.Components
public ControlPanel()
{
- RelativeSizeAxes = Axes.Both;
+ RelativeSizeAxes = Axes.Y;
AlwaysPresent = true;
- Width = 0.15f;
+ Width = TournamentSceneManager.CONTROL_AREA_WIDTH;
Anchor = Anchor.TopRight;
InternalChildren = new Drawable[]
@@ -47,8 +47,8 @@ namespace osu.Game.Tournament.Components
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Width = 0.75f,
Position = new Vector2(0, 35f),
+ Padding = new MarginPadding(5),
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5f),
},
diff --git a/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs b/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs
new file mode 100644
index 0000000000..3f5ab42fd7
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTournamentHeaderLogo.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTournamentHeaderLogo : CompositeDrawable
+ {
+ public DrawableTournamentHeaderLogo()
+ {
+ InternalChild = new LogoSprite();
+
+ Height = 82;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ private class LogoSprite : Sprite
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Texture = textures.Get("header-logo");
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
new file mode 100644
index 0000000000..bda696ba00
--- /dev/null
+++ b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Tournament.Components
+{
+ public class DrawableTournamentHeaderText : CompositeDrawable
+ {
+ public DrawableTournamentHeaderText()
+ {
+ InternalChild = new TextSprite();
+
+ Height = 22;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ private class TextSprite : Sprite
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Texture = textures.Get("header-text");
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentTitleText.cs b/osu.Game.Tournament/Components/DrawableTournamentTitleText.cs
deleted file mode 100644
index 4fbc6cd060..0000000000
--- a/osu.Game.Tournament/Components/DrawableTournamentTitleText.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Graphics;
-
-namespace osu.Game.Tournament.Components
-{
- public class DrawableTournamentTitleText : TournamentSpriteText
- {
- public DrawableTournamentTitleText()
- {
- Text = "osu!taiko world cup 2020";
- Font = OsuFont.Torus.With(size: 26, weight: FontWeight.SemiBold);
- }
- }
-}
diff --git a/osu.Game.Tournament/Components/RoundDisplay.cs b/osu.Game.Tournament/Components/RoundDisplay.cs
index dd56c83c57..bebede6782 100644
--- a/osu.Game.Tournament/Components/RoundDisplay.cs
+++ b/osu.Game.Tournament/Components/RoundDisplay.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Tournament.Components
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new DrawableTournamentTitleText(),
+ new DrawableTournamentHeaderText(),
new TournamentSpriteText
{
Text = match.Round.Value?.Name.Value ?? "Unknown Round",
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index 4116ffbec6..477bf4bd63 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -16,7 +16,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Tournament.Models;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tournament.Components
@@ -125,13 +124,21 @@ namespace osu.Game.Tournament.Components
if (!string.IsNullOrEmpty(mods))
{
- AddInternal(new Sprite
+ AddInternal(new Container
{
- Texture = textures.Get($"mods/{mods}"),
+ RelativeSizeAxes = Axes.Y,
+ Width = 60,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding(10),
- Scale = new Vector2(0.8f)
+ Child = new Sprite
+ {
+ FillMode = FillMode.Fit,
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Texture = textures.Get($"mods/{mods}"),
+ }
});
}
}
diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs
index 43088d6b92..bc66fad8c1 100644
--- a/osu.Game.Tournament/Components/TourneyVideo.cs
+++ b/osu.Game.Tournament/Components/TourneyVideo.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Video;
-using osu.Framework.Platform;
using osu.Framework.Timing;
using osu.Game.Graphics;
@@ -28,13 +27,13 @@ namespace osu.Game.Tournament.Components
}
[BackgroundDependencyLoader]
- private void load(Storage storage)
+ private void load(TournamentStorage storage)
{
- var stream = storage.GetStream($@"videos/{filename}.m4v");
+ var stream = storage.GetStream($@"videos/{filename}");
if (stream != null)
{
- InternalChild = video = new VideoSprite(stream)
+ InternalChild = video = new VideoSprite(stream, false)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index b19f2bedf0..eefa9fcfe6 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -163,12 +163,7 @@ namespace osu.Game.Tournament.IPC
{
try
{
- stableInstallPath = "G:\\My Drive\\Main\\osu!tourney";
-
- if (checkExists(stableInstallPath))
- return stableInstallPath;
-
- stableInstallPath = "G:\\My Drive\\Main\\osu!mappool";
+ stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH");
if (checkExists(stableInstallPath))
return stableInstallPath;
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
index 69a68c946b..d790f4b754 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs
@@ -2,14 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Events;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osuTK;
-using osuTK.Input;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
@@ -17,13 +14,39 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
{
private TeamScoreDisplay teamDisplay1;
private TeamScoreDisplay teamDisplay2;
+ private DrawableTournamentHeaderLogo logo;
+
+ private bool showScores = true;
public bool ShowScores
{
+ get => showScores;
set
{
- teamDisplay1.ShowScore = value;
- teamDisplay2.ShowScore = value;
+ if (value == showScores)
+ return;
+
+ showScores = value;
+
+ if (IsLoaded)
+ updateDisplay();
+ }
+ }
+
+ private bool showLogo = true;
+
+ public bool ShowLogo
+ {
+ get => showLogo;
+ set
+ {
+ if (value == showLogo)
+ return;
+
+ showLogo = value;
+
+ if (IsLoaded)
+ updateDisplay();
}
}
@@ -38,19 +61,25 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
+ Padding = new MarginPadding(20),
Spacing = new Vector2(5),
Children = new Drawable[]
{
- new DrawableTournamentTitleText
+ logo = new DrawableTournamentHeaderLogo
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(1.2f)
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Alpha = showLogo ? 1 : 0
},
- new RoundDisplay
+ new DrawableTournamentHeaderText
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ },
+ new MatchRoundDisplay
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
Scale = new Vector2(0.4f)
},
}
@@ -66,76 +95,16 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
Origin = Anchor.TopRight,
},
};
- }
- }
- public class TeamScoreDisplay : CompositeDrawable
- {
- private readonly TeamColour teamColour;
-
- private readonly Bindable currentMatch = new Bindable();
- private readonly Bindable currentTeam = new Bindable();
- private readonly Bindable currentTeamScore = new Bindable();
-
- private TeamDisplay teamDisplay;
-
- public bool ShowScore { set => teamDisplay.ShowScore = value; }
-
- public TeamScoreDisplay(TeamColour teamColour)
- {
- this.teamColour = teamColour;
-
- RelativeSizeAxes = Axes.Y;
- AutoSizeAxes = Axes.X;
+ updateDisplay();
}
- [BackgroundDependencyLoader]
- private void load(LadderInfo ladder)
+ private void updateDisplay()
{
- currentMatch.BindTo(ladder.CurrentMatch);
- currentMatch.BindValueChanged(matchChanged, true);
- }
+ teamDisplay1.ShowScore = showScores;
+ teamDisplay2.ShowScore = showScores;
- private void matchChanged(ValueChangedEvent match)
- {
- currentTeamScore.UnbindBindings();
- currentTeam.UnbindBindings();
-
- if (match.NewValue != null)
- {
- currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score);
- currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2);
- }
-
- // team may change to same team, which means score is not in a good state.
- // thus we handle this manually.
- teamChanged(currentTeam.Value);
- }
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- switch (e.Button)
- {
- case MouseButton.Left:
- if (currentTeamScore.Value < currentMatch.Value.PointsToWin)
- currentTeamScore.Value++;
- return true;
-
- case MouseButton.Right:
- if (currentTeamScore.Value > 0)
- currentTeamScore.Value--;
- return true;
- }
-
- return base.OnMouseDown(e);
- }
-
- private void teamChanged(TournamentTeam team)
- {
- InternalChildren = new Drawable[]
- {
- teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
- };
+ logo.Alpha = showLogo ? 1 : 0;
}
}
}
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/RoundDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchRoundDisplay.cs
similarity index 92%
rename from osu.Game.Tournament/Screens/Gameplay/Components/RoundDisplay.cs
rename to osu.Game.Tournament/Screens/Gameplay/Components/MatchRoundDisplay.cs
index c8b0d3bdda..87793f7e1b 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/RoundDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchRoundDisplay.cs
@@ -8,7 +8,7 @@ using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
- public class RoundDisplay : TournamentSpriteTextWithBackground
+ public class MatchRoundDisplay : TournamentSpriteTextWithBackground
{
private readonly Bindable currentMatch = new Bindable();
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
index ed14956793..2e7484542a 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
@@ -11,6 +11,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
+using osuTK;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
@@ -131,13 +132,15 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
Winning = false;
+
+ DisplayedCountSpriteText.Spacing = new Vector2(-6);
}
public bool Winning
{
set => DisplayedCountSpriteText.Font = value
- ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50)
- : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40);
+ ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true)
+ : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true);
}
}
}
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs
new file mode 100644
index 0000000000..462015f004
--- /dev/null
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+using osu.Game.Tournament.Models;
+using osuTK.Input;
+
+namespace osu.Game.Tournament.Screens.Gameplay.Components
+{
+ public class TeamScoreDisplay : CompositeDrawable
+ {
+ private readonly TeamColour teamColour;
+
+ private readonly Bindable currentMatch = new Bindable();
+ private readonly Bindable currentTeam = new Bindable();
+ private readonly Bindable currentTeamScore = new Bindable();
+
+ private TeamDisplay teamDisplay;
+
+ public bool ShowScore { set => teamDisplay.ShowScore = value; }
+
+ public TeamScoreDisplay(TeamColour teamColour)
+ {
+ this.teamColour = teamColour;
+
+ RelativeSizeAxes = Axes.Y;
+ AutoSizeAxes = Axes.X;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LadderInfo ladder)
+ {
+ currentMatch.BindTo(ladder.CurrentMatch);
+ currentMatch.BindValueChanged(matchChanged, true);
+ }
+
+ private void matchChanged(ValueChangedEvent match)
+ {
+ currentTeamScore.UnbindBindings();
+ currentTeam.UnbindBindings();
+
+ if (match.NewValue != null)
+ {
+ currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score);
+ currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2);
+ }
+
+ // team may change to same team, which means score is not in a good state.
+ // thus we handle this manually.
+ teamChanged(currentTeam.Value);
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Left:
+ if (currentTeamScore.Value < currentMatch.Value.PointsToWin)
+ currentTeamScore.Value++;
+ return true;
+
+ case MouseButton.Right:
+ if (currentTeamScore.Value > 0)
+ currentTeamScore.Value--;
+ return true;
+ }
+
+ return base.OnMouseDown(e);
+ }
+
+ private void teamChanged(TournamentTeam team)
+ {
+ InternalChildren = new Drawable[]
+ {
+ teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
index 4d770855cd..8920990d1b 100644
--- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
@@ -47,7 +47,10 @@ namespace osu.Game.Tournament.Screens.Gameplay
Loop = true,
RelativeSizeAxes = Axes.Both,
},
- header = new MatchHeader(),
+ header = new MatchHeader
+ {
+ ShowLogo = false
+ },
new Container
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
index 6f62b3ddba..534c402f6c 100644
--- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
+++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tournament.Screens.Ladder
RelativeSizeAxes = Axes.Both,
Loop = true,
},
- new DrawableTournamentTitleText
+ new DrawableTournamentHeaderText
{
Y = 100,
Anchor = Anchor.TopCentre,
diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index 4f3f7cfdbf..2b0bfe0b74 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Tournament.Screens.MapPool
new MatchHeader(),
mapFlows = new FillFlowContainer>
{
- Y = 100,
+ Y = 140,
Spacing = new Vector2(10, 10),
Padding = new MarginPadding(25),
Direction = FillDirection.Vertical,
@@ -235,6 +235,7 @@ namespace osu.Game.Tournament.Screens.MapPool
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
+ Height = 42,
});
}
}
diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs
index 0fcec645e3..88289ad6bd 100644
--- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs
+++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Tournament.Screens.Schedule
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new DrawableTournamentTitleText(),
+ new DrawableTournamentHeaderText(),
new Container
{
Margin = new MarginPadding { Top = 40 },
diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs
index 023582166c..b7f8b2bfd6 100644
--- a/osu.Game.Tournament/Screens/SetupScreen.cs
+++ b/osu.Game.Tournament/Screens/SetupScreen.cs
@@ -3,7 +3,10 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -22,6 +25,7 @@ namespace osu.Game.Tournament.Screens
private FillFlowContainer fillFlow;
private LoginOverlay loginOverlay;
+ private ActionableInfo resolution;
[Resolved]
private MatchIPCInfo ipc { get; set; }
@@ -32,9 +36,13 @@ namespace osu.Game.Tournament.Screens
[Resolved]
private RulesetStore rulesets { get; set; }
+ private Bindable windowSize;
+
[BackgroundDependencyLoader]
- private void load()
+ private void load(FrameworkConfigManager frameworkConfig)
{
+ windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize);
+
InternalChild = fillFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -48,6 +56,9 @@ namespace osu.Game.Tournament.Screens
reload();
}
+ [Resolved]
+ private Framework.Game game { get; set; }
+
private void reload()
{
var fileBasedIpc = ipc as FileBasedIPC;
@@ -97,9 +108,25 @@ namespace osu.Game.Tournament.Screens
Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset,
},
+ resolution = new ActionableInfo
+ {
+ Label = "Stream area resolution",
+ ButtonText = "Set to 1080p",
+ Action = () =>
+ {
+ windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080);
+ }
+ }
};
}
+ protected override void Update()
+ {
+ base.Update();
+
+ resolution.Value = $"{ScreenSpaceDrawQuad.Width:N0}x{ScreenSpaceDrawQuad.Height:N0}";
+ }
+
public class LabelledDropdown : LabelledComponent, T>
{
public LabelledDropdown()
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 41165ca141..85db9e61fb 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -37,6 +37,8 @@ namespace osu.Game.Tournament
private Storage storage;
+ private TournamentStorage tournamentStorage;
+
private DependencyContainer dependencies;
private Bindable windowSize;
@@ -54,14 +56,16 @@ namespace osu.Game.Tournament
{
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
- Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage))));
+ dependencies.CacheAs(tournamentStorage = new TournamentStorage(storage));
+
+ Textures.AddStore(new TextureLoaderStore(tournamentStorage));
this.storage = storage;
windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize);
windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>
{
- var minWidth = (int)(size.NewValue.Height / 9f * 16 + 400);
+ var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1;
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
}), true);
diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs
index ef8d16011d..23fcb01db7 100644
--- a/osu.Game.Tournament/TournamentSceneManager.cs
+++ b/osu.Game.Tournament/TournamentSceneManager.cs
@@ -33,6 +33,12 @@ namespace osu.Game.Tournament
private Container screens;
private TourneyVideo video;
+ public const float CONTROL_AREA_WIDTH = 160;
+
+ public const float STREAM_AREA_WIDTH = 1366;
+
+ public const double REQUIRED_WIDTH = TournamentSceneManager.CONTROL_AREA_WIDTH * 2 + TournamentSceneManager.STREAM_AREA_WIDTH;
+
[Cached]
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay();
@@ -51,13 +57,13 @@ namespace osu.Game.Tournament
{
new Container
{
- RelativeSizeAxes = Axes.Both,
- X = 200,
+ RelativeSizeAxes = Axes.Y,
+ X = CONTROL_AREA_WIDTH,
FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
- Size = new Vector2(0.8f, 1),
+ Width = STREAM_AREA_WIDTH,
//Masking = true,
Children = new Drawable[]
{
@@ -96,7 +102,7 @@ namespace osu.Game.Tournament
new Container
{
RelativeSizeAxes = Axes.Y,
- Width = 200,
+ Width = CONTROL_AREA_WIDTH,
Children = new Drawable[]
{
new Box
@@ -108,8 +114,8 @@ namespace osu.Game.Tournament
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(2),
- Padding = new MarginPadding(2),
+ Spacing = new Vector2(5),
+ Padding = new MarginPadding(5),
Children = new Drawable[]
{
new ScreenButton(typeof(SetupScreen)) { Text = "Setup", RequestSelection = SetScreen },
diff --git a/osu.Game.Tournament/TournamentStorage.cs b/osu.Game.Tournament/TournamentStorage.cs
new file mode 100644
index 0000000000..139ad3857b
--- /dev/null
+++ b/osu.Game.Tournament/TournamentStorage.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
+
+namespace osu.Game.Tournament
+{
+ internal class TournamentStorage : NamespacedResourceStore
+ {
+ public TournamentStorage(Storage storage)
+ : base(new StorageBackedResourceStore(storage), "tournament")
+ {
+ AddExtension("m4v");
+ AddExtension("avi");
+ AddExtension("mp4");
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
index 527f520172..3420fcf260 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps.Formats
{
@@ -26,17 +25,5 @@ namespace osu.Game.Beatmaps.Formats
AddDecoder(@"osu file format v", m => new LegacyDifficultyCalculatorBeatmapDecoder(int.Parse(m.Split('v').Last())));
SetFallbackDecoder(() => new LegacyDifficultyCalculatorBeatmapDecoder());
}
-
- protected override TimingControlPoint CreateTimingControlPoint()
- => new LegacyDifficultyCalculatorTimingControlPoint();
-
- private class LegacyDifficultyCalculatorTimingControlPoint : TimingControlPoint
- {
- public LegacyDifficultyCalculatorTimingControlPoint()
- {
- BeatLengthBindable.MinValue = double.MinValue;
- BeatLengthBindable.MaxValue = double.MaxValue;
- }
- }
}
}
diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs
index 841936d2c5..7c78141b4d 100644
--- a/osu.Game/Graphics/OsuFont.cs
+++ b/osu.Game/Graphics/OsuFont.cs
@@ -30,8 +30,15 @@ namespace osu.Game.Graphics
/// Whether the font is italic.
/// Whether all characters should be spaced the same distance apart.
/// The .
- public static FontUsage GetFont(Typeface typeface = Typeface.Exo, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false)
- => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), italics, fixedWidth);
+ public static FontUsage GetFont(Typeface typeface = Typeface.Torus, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false)
+ => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), getItalics(italics), fixedWidth);
+
+ private static bool getItalics(in bool italicsRequested)
+ {
+ // right now none of our fonts support italics.
+ // should add exceptions to this rule if they come up.
+ return false;
+ }
///
/// Retrieves the string representation of a .
@@ -42,9 +49,6 @@ namespace osu.Game.Graphics
{
switch (typeface)
{
- case Typeface.Exo:
- return "Exo2.0";
-
case Typeface.Venera:
return "Venera";
@@ -62,7 +66,13 @@ namespace osu.Game.Graphics
/// The .
/// The string representation of in the specified .
public static string GetWeightString(Typeface typeface, FontWeight weight)
- => GetWeightString(GetFamilyString(typeface), weight);
+ {
+ if (typeface == Typeface.Torus && weight == FontWeight.Medium)
+ // torus doesn't have a medium; fallback to regular.
+ weight = FontWeight.Regular;
+
+ return GetWeightString(GetFamilyString(typeface), weight);
+ }
///
/// Retrieves the string representation of a .
@@ -96,7 +106,6 @@ namespace osu.Game.Graphics
public enum Typeface
{
- Exo,
Venera,
Torus
}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 6c883d9893..ca9f1330f9 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -173,7 +173,7 @@ namespace osu.Game.Graphics.UserInterface
new HoverClickSounds()
};
- Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
+ Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
protected override void OnActivated() => fadeActive();
diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs
index ddcb626701..d05a08108a 100644
--- a/osu.Game/Graphics/UserInterface/PageTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface
new HoverClickSounds()
};
- Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
+ Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString();
diff --git a/osu.Game/Online/Chat/ChannelType.cs b/osu.Game/Online/Chat/ChannelType.cs
index 7d2b661164..151efc4645 100644
--- a/osu.Game/Online/Chat/ChannelType.cs
+++ b/osu.Game/Online/Chat/ChannelType.cs
@@ -12,5 +12,6 @@ namespace osu.Game.Online.Chat
Temporary,
PM,
Group,
+ System,
}
}
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index b2277e2abf..3c7ab27651 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -138,30 +138,17 @@ namespace osu.Game
dependencies.Cache(LocalConfig);
AddFont(Resources, @"Fonts/osuFont");
- AddFont(Resources, @"Fonts/Exo2.0-Medium");
- AddFont(Resources, @"Fonts/Exo2.0-MediumItalic");
+
+ AddFont(Resources, @"Fonts/Torus-Regular");
+ AddFont(Resources, @"Fonts/Torus-Light");
+ AddFont(Resources, @"Fonts/Torus-SemiBold");
+ AddFont(Resources, @"Fonts/Torus-Bold");
AddFont(Resources, @"Fonts/Noto-Basic");
AddFont(Resources, @"Fonts/Noto-Hangul");
AddFont(Resources, @"Fonts/Noto-CJK-Basic");
AddFont(Resources, @"Fonts/Noto-CJK-Compatibility");
- AddFont(Resources, @"Fonts/Exo2.0-Regular");
- AddFont(Resources, @"Fonts/Exo2.0-RegularItalic");
- AddFont(Resources, @"Fonts/Exo2.0-SemiBold");
- AddFont(Resources, @"Fonts/Exo2.0-SemiBoldItalic");
- AddFont(Resources, @"Fonts/Exo2.0-Bold");
- AddFont(Resources, @"Fonts/Exo2.0-BoldItalic");
- AddFont(Resources, @"Fonts/Exo2.0-Light");
- AddFont(Resources, @"Fonts/Exo2.0-LightItalic");
- AddFont(Resources, @"Fonts/Exo2.0-Black");
- AddFont(Resources, @"Fonts/Exo2.0-BlackItalic");
-
- AddFont(Resources, @"Fonts/Torus-SemiBold");
- AddFont(Resources, @"Fonts/Torus-Bold");
- AddFont(Resources, @"Fonts/Torus-Regular");
- AddFont(Resources, @"Fonts/Torus-Light");
-
AddFont(Resources, @"Fonts/Venera-Light");
AddFont(Resources, @"Fonts/Venera-Bold");
AddFont(Resources, @"Fonts/Venera-Black");
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index 2576900db8..a0b1b27ebf 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -132,7 +132,7 @@ namespace osu.Game.Overlays.AccountCreation
usernameDescription.AddText("This will be your public presence. No profanity, no impersonation. Avoid exposing your own personal details, too!");
emailAddressDescription.AddText("Will be used for notifications, account verification and in the case you forget your password. No spam, ever.");
- emailAddressDescription.AddText(" Make sure to get it right!", cp => cp.Font = cp.Font.With(Typeface.Exo, weight: FontWeight.Bold));
+ emailAddressDescription.AddText(" Make sure to get it right!", cp => cp.Font = cp.Font.With(Typeface.Torus, weight: FontWeight.Bold));
passwordDescription.AddText("At least ");
characterCheckText = passwordDescription.AddText("8 characters long");
diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
index 8aee76cb08..48bf6c2ddd 100644
--- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
@@ -93,6 +93,7 @@ namespace osu.Game.Overlays.Changelog
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
+ TextAnchor = Anchor.BottomLeft,
}
}
};
@@ -125,7 +126,7 @@ namespace osu.Game.Overlays.Changelog
title.AddText("by ", t =>
{
- t.Font = fontMedium.With(italics: true);
+ t.Font = fontMedium;
t.Colour = entryColour;
t.Padding = new MarginPadding { Left = 10 };
});
@@ -138,7 +139,7 @@ namespace osu.Game.Overlays.Changelog
Id = entry.GithubUser.UserId.Value
}, t =>
{
- t.Font = fontMedium.With(italics: true);
+ t.Font = fontMedium;
t.Colour = entryColour;
});
}
@@ -146,7 +147,7 @@ namespace osu.Game.Overlays.Changelog
{
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
{
- t.Font = fontMedium.With(italics: true);
+ t.Font = fontMedium;
t.Colour = entryColour;
});
}
@@ -154,7 +155,7 @@ namespace osu.Game.Overlays.Changelog
{
title.AddText(entry.GithubUser.DisplayName, t =>
{
- t.Font = fontMedium.With(italics: true);
+ t.Font = fontMedium;
t.Colour = entryColour;
});
}
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
index d5d9a6c2ce..e3ede04edd 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
@@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Chat.Tabs
public ChannelSelectorTabChannel()
{
Name = "+";
+ Type = ChannelType.System;
}
}
}
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
index 104495ae01..a72f182450 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Chat.Tabs
// performTabSort might've made selectorTab's position wonky, fix it
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
- ((ChannelTabItem)item).OnRequestClose += tabCloseRequested;
+ ((ChannelTabItem)item).OnRequestClose += channelItem => OnRequestLeave?.Invoke(channelItem.Value);
base.AddTabItem(item, addToDropdown);
}
@@ -74,18 +74,24 @@ namespace osu.Game.Overlays.Chat.Tabs
///
/// Removes a channel from the ChannelTabControl.
- /// If the selected channel is the one that is beeing removed, the next available channel will be selected.
+ /// If the selected channel is the one that is being removed, the next available channel will be selected.
///
/// The channel that is going to be removed.
public void RemoveChannel(Channel channel)
{
- RemoveItem(channel);
-
if (Current.Value == channel)
{
- // Prefer non-selector channels first
- Current.Value = Items.FirstOrDefault(c => !(c is ChannelSelectorTabItem.ChannelSelectorTabChannel)) ?? Items.FirstOrDefault();
+ var allChannels = TabContainer.AllTabItems.Select(tab => tab.Value).ToList();
+ var isNextTabSelector = allChannels[allChannels.IndexOf(channel) + 1] == selectorTab.Value;
+
+ // selectorTab is not switchable, so we have to explicitly select it if it's the only tab left
+ if (isNextTabSelector && allChannels.Count == 2)
+ SelectTab(selectorTab);
+ else
+ SwitchTab(isNextTabSelector ? -1 : 1);
}
+
+ RemoveItem(channel);
}
protected override void SelectTab(TabItem tab)
@@ -100,21 +106,6 @@ namespace osu.Game.Overlays.Chat.Tabs
selectorTab.Active.Value = false;
}
- private void tabCloseRequested(TabItem tab)
- {
- int totalTabs = TabContainer.Count - 1; // account for selectorTab
- int currentIndex = Math.Clamp(TabContainer.IndexOf(tab), 1, totalTabs);
-
- if (tab == SelectedTab && totalTabs > 1)
- // Select the tab after tab-to-be-removed's index, or the tab before if current == last
- SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
- else if (totalTabs == 1 && !selectorTab.Active.Value)
- // Open channel selection overlay if all channel tabs will be closed after removing this tab
- SelectTab(selectorTab);
-
- OnRequestLeave?.Invoke(tab.Value);
- }
-
protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer
{
Direction = FillDirection.Full,
diff --git a/osu.Game/Overlays/News/NewsArticleCover.cs b/osu.Game/Overlays/News/NewsArticleCover.cs
index f61b30b381..e381b629e4 100644
--- a/osu.Game/Overlays/News/NewsArticleCover.cs
+++ b/osu.Game/Overlays/News/NewsArticleCover.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Overlays.News
Left = 25,
Bottom = 50,
},
- Font = OsuFont.GetFont(Typeface.Exo, 24, FontWeight.Bold),
+ Font = OsuFont.GetFont(Typeface.Torus, 24, FontWeight.Bold),
Text = info.Title,
},
new OsuSpriteText
@@ -87,7 +87,7 @@ namespace osu.Game.Overlays.News
Left = 25,
Bottom = 30,
},
- Font = OsuFont.GetFont(Typeface.Exo, 16, FontWeight.Bold),
+ Font = OsuFont.GetFont(Typeface.Torus, 16, FontWeight.Bold),
Text = "by " + info.Author
}
};
@@ -148,7 +148,7 @@ namespace osu.Game.Overlays.News
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Font = OsuFont.GetFont(Typeface.Exo, 12, FontWeight.Black, false, false),
+ Font = OsuFont.GetFont(Typeface.Torus, 12, FontWeight.Black, false, false),
Text = date.ToString("d MMM yyy").ToUpper(),
Margin = new MarginPadding
{
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 4bff8146b4..3478f18a40 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -14,22 +14,8 @@ namespace osu.Game.Overlays.Volume
public Func ActionRequested;
public Func ScrollActionRequested;
- public bool OnPressed(GlobalAction action)
- {
- // if nothing else handles selection actions in the game, it's safe to let volume be adjusted.
- switch (action)
- {
- case GlobalAction.SelectPrevious:
- action = GlobalAction.IncreaseVolume;
- break;
-
- case GlobalAction.SelectNext:
- action = GlobalAction.DecreaseVolume;
- break;
- }
-
- return ActionRequested?.Invoke(action) ?? false;
- }
+ public bool OnPressed(GlobalAction action) =>
+ ActionRequested?.Invoke(action) ?? false;
public bool OnScroll(GlobalAction action, float amount, bool isPrecise) =>
ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 1fc51d2ce8..8d3ad5984f 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 7)
{
- length = Math.Max(0, Parsing.ParseDouble(split[7]));
+ length = Math.Max(0, Parsing.ParseDouble(split[7], Parsing.MAX_COORDINATE_VALUE));
if (length == 0)
length = null;
}
diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs
index ee8200321b..35091028ae 100644
--- a/osu.Game/Screens/Menu/Disclaimer.cs
+++ b/osu.Game/Screens/Menu/Disclaimer.cs
@@ -90,14 +90,14 @@ namespace osu.Game.Screens.Menu
}
};
- textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Exo, 30, FontWeight.Light));
- textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Exo, 30, FontWeight.SemiBold));
+ textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Light));
+ textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.SemiBold));
textFlow.NewParagraph();
static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold);
- textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Exo, 20, FontWeight.SemiBold));
+ textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold));
textFlow.NewParagraph();
textFlow.NewParagraph();
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index 4e51ff939a..be5762e68d 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -259,11 +259,18 @@ namespace osu.Game.Screens.Menu
private class LazerLogo : CompositeDrawable
{
+ private readonly Stream videoStream;
+
public LazerLogo(Stream videoStream)
{
+ this.videoStream = videoStream;
Size = new Vector2(960);
+ }
- InternalChild = new VideoSprite(videoStream)
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new VideoSprite(videoStream, false)
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 }
diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs
index a55db096af..4152a9a3b2 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
- hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true));
+ hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold, italics: true));
flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both };
}
diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
index f8fb192b5c..0d31805774 100644
--- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
+++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
rankText.AddText($"#{index + 1} ", s =>
{
- s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold);
+ s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold);
s.Colour = colours.YellowDark;
});
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index ca20b02bce..92efde45ad 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -153,9 +153,11 @@ namespace osu.Game.Screens.Select
beatmaps.BeatmapHidden += beatmapHidden;
beatmaps.BeatmapRestored += beatmapRestored;
- loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable());
+ loadBeatmapSets(GetLoadableBeatmaps());
}
+ protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable();
+
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
{
var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID);
@@ -201,9 +203,6 @@ namespace osu.Game.Screens.Select
///
/// Selects a given beatmap on the carousel.
- ///
- /// If bypassFilters is false, we will try to select another unfiltered beatmap in the same set. If the
- /// entire set is filtered, no selection is made.
///
/// The beatmap to select.
/// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel).
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index e0d59e3b18..6d760df065 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select.Carousel
if (Beatmap.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
{
- // bypass filtering for selected beatmap
+ // only check ruleset equality or convertability for selected beatmap
Filtered.Value = !match;
return;
}
diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs
index fa4de21eec..52328d43b2 100644
--- a/osu.Game/Skinning/LegacySkinExtensions.cs
+++ b/osu.Game/Skinning/LegacySkinExtensions.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Skinning
{
var iniRate = source.GetConfig(GlobalSkinConfiguration.AnimationFramerate);
- if (iniRate != null)
+ if (iniRate?.Value > 0)
return 1000f / iniRate.Value;
return 1000f / textures.Length;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index cc1ab654ab..647f05b428 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 04b688cfa3..0e5c64cf0f 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -79,7 +79,7 @@
-
+