mirror of
https://github.com/ppy/osu.git
synced 2025-03-26 10:07:19 +08:00
Merge branch 'master' into taiko-drumroll-skinning
This commit is contained in:
commit
a842f17630
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.411.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.417.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
132
osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
Normal file
132
osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public class TestSceneHyperDashColouring : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestDefaultFruitColour()
|
||||
{
|
||||
var skin = new TestSkin();
|
||||
|
||||
checkHyperDashFruitColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomFruitColour()
|
||||
{
|
||||
var skin = new TestSkin
|
||||
{
|
||||
HyperDashFruitColour = Color4.Cyan
|
||||
};
|
||||
|
||||
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomFruitColourPriority()
|
||||
{
|
||||
var skin = new TestSkin
|
||||
{
|
||||
HyperDashColour = Color4.Goldenrod,
|
||||
HyperDashFruitColour = Color4.Cyan
|
||||
};
|
||||
|
||||
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFruitColourFallback()
|
||||
{
|
||||
var skin = new TestSkin
|
||||
{
|
||||
HyperDashColour = Color4.Goldenrod
|
||||
};
|
||||
|
||||
checkHyperDashFruitColour(skin, skin.HyperDashColour);
|
||||
}
|
||||
|
||||
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
|
||||
{
|
||||
DrawableFruit drawableFruit = null;
|
||||
|
||||
AddStep("create hyper-dash fruit", () =>
|
||||
{
|
||||
var fruit = new Fruit { HyperDashTarget = new Banana() };
|
||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(4f),
|
||||
}, skin);
|
||||
});
|
||||
|
||||
AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour));
|
||||
}
|
||||
|
||||
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
|
||||
{
|
||||
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
|
||||
var testSkinProvider = new SkinProvidingContainer(skin);
|
||||
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
|
||||
|
||||
return legacySkinProvider
|
||||
.WithChild(testSkinProvider
|
||||
.WithChild(legacySkinTransformer
|
||||
.WithChild(child)));
|
||||
}
|
||||
|
||||
private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
|
||||
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Sprite>().Any(c => c.Colour == expectedColour);
|
||||
|
||||
private class TestSkin : LegacySkin
|
||||
{
|
||||
public Color4 HyperDashColour
|
||||
{
|
||||
get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()];
|
||||
set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value;
|
||||
}
|
||||
|
||||
public Color4 HyperDashAfterImageColour
|
||||
{
|
||||
get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()];
|
||||
set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value;
|
||||
}
|
||||
|
||||
public Color4 HyperDashFruitColour
|
||||
{
|
||||
get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()];
|
||||
set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value;
|
||||
}
|
||||
|
||||
public TestSkin()
|
||||
: base(new SkinInfo(), null, null, string.Empty)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Color4.Red,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 12f * RADIUS_ADJUST,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Red,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -65,6 +65,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
|
||||
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source.GetConfig<TLookup, TValue>(lookup);
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case CatchSkinColour colour:
|
||||
return source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||
}
|
||||
|
||||
return source.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
Normal file
23
osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
public enum CatchSkinColour
|
||||
{
|
||||
/// <summary>
|
||||
/// The colour to be used for the catcher while in hyper-dashing state.
|
||||
/// </summary>
|
||||
HyperDash,
|
||||
|
||||
/// <summary>
|
||||
/// The colour to be used for fruits that grant the catcher the ability to hyper-dash.
|
||||
/// </summary>
|
||||
HyperDashFruit,
|
||||
|
||||
/// <summary>
|
||||
/// The colour to be used for the "exploding" catcher sprite on beginning of hyper-dashing.
|
||||
/// </summary>
|
||||
HyperDashAfterImage,
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
var hyperDash = new Sprite
|
||||
{
|
||||
Texture = skin.GetTexture(lookupName),
|
||||
Colour = Color4.Red,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Depth = 1,
|
||||
Alpha = 0.7f,
|
||||
Scale = new Vector2(1.2f)
|
||||
Scale = new Vector2(1.2f),
|
||||
Texture = skin.GetTexture(lookupName),
|
||||
Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||
Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
};
|
||||
|
||||
AddInternal(hyperDash);
|
||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
||||
{
|
||||
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are hyper-dashing or not.
|
||||
/// </summary>
|
||||
|
106
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
Normal file
106
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModHidden : ModTestScene
|
||||
{
|
||||
public TestSceneOsuModHidden()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModHidden(),
|
||||
Autoplay = true,
|
||||
PassCondition = checkSomeHit
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModHidden(),
|
||||
Autoplay = true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
EndTime = 1000,
|
||||
},
|
||||
new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1200,
|
||||
EndTime = 2200,
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
Position = new Vector2(300, 192),
|
||||
StartTime = 3200,
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
Position = new Vector2(384, 192),
|
||||
StartTime = 4200,
|
||||
}
|
||||
}
|
||||
},
|
||||
PassCondition = checkSomeHit
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModHidden(),
|
||||
Autoplay = true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
EndTime = 1000,
|
||||
},
|
||||
new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1200,
|
||||
EndTime = 2200,
|
||||
},
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3200,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
},
|
||||
new Slider
|
||||
{
|
||||
StartTime = 5200,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
}
|
||||
}
|
||||
},
|
||||
PassCondition = checkSomeHit
|
||||
});
|
||||
|
||||
private bool checkSomeHit()
|
||||
{
|
||||
return Player.ScoreProcessor.JudgedHits >= 4;
|
||||
}
|
||||
}
|
||||
}
|
@ -296,6 +296,44 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitSliderHeadBeforeHitCircle()
|
||||
{
|
||||
const double time_circle = 1000;
|
||||
const double time_slider = 1200;
|
||||
Vector2 positionCircle = Vector2.Zero;
|
||||
Vector2 positionSlider = new Vector2(80);
|
||||
|
||||
var hitObjects = new List<OsuHitObject>
|
||||
{
|
||||
new TestHitCircle
|
||||
{
|
||||
StartTime = time_circle,
|
||||
Position = positionCircle
|
||||
},
|
||||
new TestSlider
|
||||
{
|
||||
StartTime = time_slider,
|
||||
Position = positionSlider,
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(25, 0),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
performTest(hitObjects, new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } },
|
||||
new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } },
|
||||
new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } },
|
||||
});
|
||||
|
||||
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||
}
|
||||
|
||||
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||
@ -371,6 +409,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
HeadCircle.HitWindows = new TestHitWindows();
|
||||
TailCircle.HitWindows = new TestHitWindows();
|
||||
|
||||
HeadCircle.HitWindows.SetDifficulty(0);
|
||||
TailCircle.HitWindows.SetDifficulty(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
private const double fade_in_duration_multiplier = 0.4;
|
||||
private const double fade_out_duration_multiplier = 0.3;
|
||||
|
||||
protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner);
|
||||
|
||||
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
||||
|
@ -125,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
return new DrawableSliderTail(slider, tail);
|
||||
|
||||
case SliderHeadCircle head:
|
||||
return new DrawableSliderHead(slider, head) { OnShake = Shake };
|
||||
return new DrawableSliderHead(slider, head)
|
||||
{
|
||||
OnShake = Shake,
|
||||
CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
|
||||
};
|
||||
|
||||
case SliderTick tick:
|
||||
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
|
||||
|
@ -1,16 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that <see cref="HitObject"/>s are hit in-order.
|
||||
/// Ensures that <see cref="HitObject"/>s are hit in-order. Affectionately known as "note lock".
|
||||
/// If a <see cref="HitObject"/> is hit out of order:
|
||||
/// <list type="number">
|
||||
/// <item><description>The hit is blocked if it occurred earlier than the previous <see cref="HitObject"/>'s start time.</description></item>
|
||||
@ -36,13 +37,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
DrawableHitObject blockingObject = null;
|
||||
|
||||
// Find the last hitobject which blocks future hits.
|
||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||
{
|
||||
if (obj == hitObject)
|
||||
break;
|
||||
|
||||
if (drawableCanBlockFutureHits(obj))
|
||||
if (hitObjectCanBlockFutureHits(obj))
|
||||
blockingObject = obj;
|
||||
}
|
||||
|
||||
@ -54,74 +51,56 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
// 1. The last blocking hitobject has been judged.
|
||||
// 2. The current time is after the last hitobject's start time.
|
||||
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
|
||||
if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||
public void HandleHit(HitObject hitObject)
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
{
|
||||
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
|
||||
if (!hitObjectCanBlockFutureHits(hitObject))
|
||||
return;
|
||||
|
||||
double maximumTime = hitObject.StartTime;
|
||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||
|
||||
// Iterate through and apply miss results to all top-level and nested hitobjects which block future hits.
|
||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||
{
|
||||
if (obj.Judged || obj.HitObject.StartTime >= maximumTime)
|
||||
if (obj.Judged)
|
||||
continue;
|
||||
|
||||
if (hitObjectCanBlockFutureHits(obj.HitObject))
|
||||
applyMiss(obj);
|
||||
|
||||
foreach (var nested in obj.NestedHitObjects)
|
||||
{
|
||||
if (nested.Judged || nested.HitObject.StartTime >= maximumTime)
|
||||
continue;
|
||||
|
||||
if (hitObjectCanBlockFutureHits(nested.HitObject))
|
||||
applyMiss(nested);
|
||||
}
|
||||
if (hitObjectCanBlockFutureHits(obj))
|
||||
((DrawableOsuHitObject)obj).MissForcefully();
|
||||
}
|
||||
|
||||
static void applyMiss(DrawableHitObject obj) => ((DrawableOsuHitObject)obj).MissForcefully();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="DrawableHitObject"/> blocks hits on future <see cref="DrawableHitObject"/>s until its start time is reached.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will ONLY match on top-most <see cref="DrawableHitObject"/>s.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to test.</param>
|
||||
private static bool drawableCanBlockFutureHits(DrawableHitObject hitObject)
|
||||
{
|
||||
// Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over.
|
||||
return hitObject is DrawableHitCircle || hitObject is DrawableSlider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is more rigorous and may not match on top-most <see cref="HitObject"/>s as <see cref="drawableCanBlockFutureHits"/> does.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to test.</param>
|
||||
private static bool hitObjectCanBlockFutureHits(HitObject hitObject)
|
||||
{
|
||||
// Unlike the above we will receive slider tails, but they do not block future hits.
|
||||
if (hitObject is SliderTailCircle)
|
||||
return false;
|
||||
private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
|
||||
=> hitObject is DrawableHitCircle;
|
||||
|
||||
// All other hitcircles continue to block future hits.
|
||||
return hitObject is HitCircle;
|
||||
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||
{
|
||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||
{
|
||||
if (obj.HitObject.StartTime >= targetTime)
|
||||
yield break;
|
||||
|
||||
yield return obj;
|
||||
|
||||
foreach (var nestedObj in obj.NestedHitObjects)
|
||||
{
|
||||
if (nestedObj.HitObject.StartTime >= targetTime)
|
||||
break;
|
||||
|
||||
yield return nestedObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
||||
hitPolicy.HandleHit(result.HitObject);
|
||||
hitPolicy.HandleHit(judgedObject);
|
||||
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
@ -29,11 +29,17 @@ namespace osu.Game.Tests.NonVisual
|
||||
var cpi = new ControlPointInfo();
|
||||
|
||||
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
|
||||
cpi.Add(1000, new TimingControlPoint()); // is redundant
|
||||
cpi.Add(1000, new TimingControlPoint()); // is also not redundant, due to change of offset
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
|
||||
cpi.Add(1000, new TimingControlPoint()); //is redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -86,11 +92,12 @@ namespace osu.Game.Tests.NonVisual
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||
|
||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant
|
||||
cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
98
osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
Normal file
98
osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneKeyBindings : OsuManualInputManagerTestScene
|
||||
{
|
||||
private readonly ActionReceiver receiver;
|
||||
|
||||
public TestSceneKeyBindings()
|
||||
{
|
||||
Add(new TestKeyBindingContainer
|
||||
{
|
||||
Child = receiver = new ActionReceiver()
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultsWhenNotDatabased()
|
||||
{
|
||||
AddStep("fire key", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.A);
|
||||
InputManager.ReleaseKey(Key.A);
|
||||
});
|
||||
|
||||
AddAssert("received key", () => receiver.ReceivedAction);
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) =>
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new KeyBinding(InputKey.A, TestAction.Down),
|
||||
};
|
||||
}
|
||||
|
||||
public override string Description => "test";
|
||||
public override string ShortName => "test";
|
||||
}
|
||||
|
||||
private enum TestAction
|
||||
{
|
||||
Down,
|
||||
}
|
||||
|
||||
private class TestKeyBindingContainer : DatabasedKeyBindingContainer<TestAction>
|
||||
{
|
||||
public TestKeyBindingContainer()
|
||||
: base(new TestRuleset().RulesetInfo, 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionReceiver : CompositeDrawable, IKeyBindingHandler<TestAction>
|
||||
{
|
||||
public bool ReceivedAction;
|
||||
|
||||
public bool OnPressed(TestAction action)
|
||||
{
|
||||
ReceivedAction = action == TestAction.Down;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnReleased(TestAction action)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
Normal file
43
osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dashboard;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneDashboardOverlay : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DashboardOverlay),
|
||||
typeof(DashboardOverlayHeader),
|
||||
typeof(FriendDisplay)
|
||||
};
|
||||
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
private readonly DashboardOverlay overlay;
|
||||
|
||||
public TestSceneDashboardOverlay()
|
||||
{
|
||||
Add(overlay = new DashboardOverlay());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShow()
|
||||
{
|
||||
AddStep("Show", overlay.Show);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHide()
|
||||
{
|
||||
AddStep("Hide", overlay.Hide);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ using osu.Game.Users;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private FriendDisplay display;
|
||||
private TestFriendDisplay display;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = display = new FriendDisplay()
|
||||
Child = display = new TestFriendDisplay()
|
||||
};
|
||||
});
|
||||
|
||||
@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online
|
||||
LastVisit = DateTimeOffset.Now
|
||||
}
|
||||
};
|
||||
|
||||
private class TestFriendDisplay : FriendDisplay
|
||||
{
|
||||
public void Fetch()
|
||||
{
|
||||
base.APIStateChanged(API, APIState.Online);
|
||||
}
|
||||
|
||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
Normal file
85
osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneNowPlayingCommand : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(IChannelPostTarget))]
|
||||
private PostTarget postTarget { get; set; }
|
||||
|
||||
public TestSceneNowPlayingCommand()
|
||||
{
|
||||
Add(postTarget = new PostTarget());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGenericActivity()
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is listening"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEditActivity()
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is editing"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayActivity()
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo()));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is playing"));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestLinkPresence(bool hasOnlineId)
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
|
||||
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
|
||||
});
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
|
||||
if (hasOnlineId)
|
||||
AddAssert("Check link presence", () => postTarget.LastMessage.Contains("https://osu.ppy.sh/b/1234"));
|
||||
else
|
||||
AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://"));
|
||||
}
|
||||
|
||||
public class PostTarget : Component, IChannelPostTarget
|
||||
{
|
||||
public void PostMessage(string text, bool isAction = false, Channel target = null)
|
||||
{
|
||||
LastMessage = text;
|
||||
}
|
||||
|
||||
public string LastMessage { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -359,6 +359,68 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPresentNewRulesetNewBeatmap()
|
||||
{
|
||||
createSongSelect();
|
||||
changeRuleset(2);
|
||||
|
||||
addRulesetImportStep(2);
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
|
||||
|
||||
addRulesetImportStep(0);
|
||||
addRulesetImportStep(0);
|
||||
addRulesetImportStep(0);
|
||||
|
||||
BeatmapInfo target = null;
|
||||
|
||||
AddStep("select beatmap/ruleset externally", () =>
|
||||
{
|
||||
target = manager.GetAllUsableBeatmapSets()
|
||||
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
|
||||
|
||||
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
|
||||
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
||||
});
|
||||
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||
|
||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPresentNewBeatmapNewRuleset()
|
||||
{
|
||||
createSongSelect();
|
||||
changeRuleset(2);
|
||||
|
||||
addRulesetImportStep(2);
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
|
||||
|
||||
addRulesetImportStep(0);
|
||||
addRulesetImportStep(0);
|
||||
addRulesetImportStep(0);
|
||||
|
||||
BeatmapInfo target = null;
|
||||
|
||||
AddStep("select beatmap/ruleset externally", () =>
|
||||
{
|
||||
target = manager.GetAllUsableBeatmapSets()
|
||||
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
|
||||
|
||||
Beatmap.Value = manager.GetWorkingBeatmap(target);
|
||||
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
|
||||
});
|
||||
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||
|
||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
||||
|
||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetChangeResetsMods()
|
||||
{
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual
|
||||
typeof(OnScreenDisplay),
|
||||
typeof(NotificationOverlay),
|
||||
typeof(DirectOverlay),
|
||||
typeof(SocialOverlay),
|
||||
typeof(DashboardOverlay),
|
||||
typeof(ChannelManager),
|
||||
typeof(ChatOverlay),
|
||||
typeof(SettingsOverlay),
|
||||
|
@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint>
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
@ -19,12 +19,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this control point is equivalent to another, ignoring time.
|
||||
/// Determines whether this <see cref="ControlPoint"/> results in a meaningful change when placed alongside another.
|
||||
/// </summary>
|
||||
/// <param name="other">Another control point to compare with.</param>
|
||||
/// <returns>Whether equivalent.</returns>
|
||||
public abstract bool EquivalentTo(ControlPoint other);
|
||||
|
||||
public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other);
|
||||
/// <param name="existing">An existing control point to compare with.</param>
|
||||
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
||||
public abstract bool IsRedundant(ControlPoint existing);
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
break;
|
||||
}
|
||||
|
||||
return existing?.EquivalentTo(newPoint) == true;
|
||||
return newPoint?.IsRedundant(existing) == true;
|
||||
}
|
||||
|
||||
private void groupItemAdded(ControlPoint controlPoint)
|
||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => SpeedMultiplierBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier);
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> existing is DifficultyControlPoint existingDifficulty
|
||||
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => KiaiModeBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is EffectControlPoint otherTyped &&
|
||||
KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> !OmitFirstBarLine
|
||||
&& existing is EffectControlPoint existingEffect
|
||||
&& KiaiMode == existingEffect.KiaiMode
|
||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
return newSampleInfo;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is SampleControlPoint otherTyped &&
|
||||
SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> existing is SampleControlPoint existingSample
|
||||
&& SampleBank == existingSample.SampleBank
|
||||
&& SampleVolume == existingSample.SampleVolume;
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
public double BPM => 60000 / BeatLength;
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is TimingControlPoint otherTyped
|
||||
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
|
||||
// Timing points are never redundant as they can change the time signature.
|
||||
public override bool IsRedundant(ControlPoint existing) => false;
|
||||
}
|
||||
}
|
||||
|
@ -187,27 +187,13 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
writer.WriteLine("[HitObjects]");
|
||||
|
||||
// TODO: implement other legacy rulesets
|
||||
switch (beatmap.BeatmapInfo.RulesetID)
|
||||
{
|
||||
case 0:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleOsuHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleTaikoHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleCatchHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleManiaHitObject(writer, h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,12 +314,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||
{
|
||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||
|
@ -150,7 +150,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
HitObjects,
|
||||
Variables,
|
||||
Fonts,
|
||||
Mania
|
||||
CatchTheBeat,
|
||||
Mania,
|
||||
}
|
||||
|
||||
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
|
||||
@ -178,9 +179,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped &&
|
||||
CustomSampleBank == otherTyped.CustomSampleBank;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> base.IsRedundant(existing)
|
||||
&& existing is LegacySampleControlPoint existingSample
|
||||
&& CustomSampleBank == existingSample.CustomSampleBank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||
|
||||
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
|
||||
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||
return;
|
||||
|
||||
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
|
||||
|
@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
|
||||
store.KeyBindingChanged -= ReloadMappings;
|
||||
}
|
||||
|
||||
protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList();
|
||||
protected override void ReloadMappings()
|
||||
{
|
||||
if (ruleset != null && !ruleset.ID.HasValue)
|
||||
// if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
|
||||
// fallback to defaults instead.
|
||||
KeyBindings = DefaultKeyBindings;
|
||||
else
|
||||
KeyBindings = store.Query(ruleset?.ID, variant).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,24 +18,32 @@ namespace osu.Game.Online.API
|
||||
|
||||
public T Result { get; private set; }
|
||||
|
||||
protected APIRequest()
|
||||
{
|
||||
base.Success += () => TriggerSuccess(((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked on successful completion of an API request.
|
||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||
/// </summary>
|
||||
public new event APISuccessHandler<T> Success;
|
||||
|
||||
protected override void PostProcess()
|
||||
{
|
||||
base.PostProcess();
|
||||
Result = ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
|
||||
}
|
||||
|
||||
internal void TriggerSuccess(T result)
|
||||
{
|
||||
if (Result != null)
|
||||
throw new InvalidOperationException("Attempted to trigger success more than once");
|
||||
|
||||
Result = result;
|
||||
Success?.Invoke(result);
|
||||
|
||||
TriggerSuccess();
|
||||
}
|
||||
|
||||
internal override void TriggerSuccess()
|
||||
{
|
||||
base.TriggerSuccess();
|
||||
Success?.Invoke(Result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +107,8 @@ namespace osu.Game.Online.API
|
||||
if (checkAndScheduleFailure())
|
||||
return;
|
||||
|
||||
PostProcess();
|
||||
|
||||
API.Schedule(delegate
|
||||
{
|
||||
if (cancelled) return;
|
||||
@ -107,7 +117,14 @@ namespace osu.Game.Online.API
|
||||
});
|
||||
}
|
||||
|
||||
internal void TriggerSuccess()
|
||||
/// <summary>
|
||||
/// Perform any post-processing actions after a successful request.
|
||||
/// </summary>
|
||||
protected virtual void PostProcess()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void TriggerSuccess()
|
||||
{
|
||||
Success?.Invoke();
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
|
||||
/// <summary>
|
||||
/// Manages everything channel related
|
||||
/// </summary>
|
||||
public class ChannelManager : PollingComponent
|
||||
public class ChannelManager : PollingComponent, IChannelPostTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// The channels the player joins on startup
|
||||
@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "np":
|
||||
AddInternal(new NowPlayingCommand());
|
||||
break;
|
||||
|
||||
case "me":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
@ -234,7 +238,7 @@ namespace osu.Game.Online.Chat
|
||||
break;
|
||||
|
||||
case "help":
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
[Cached(typeof(IChannelPostTarget))]
|
||||
public interface IChannelPostTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Posts a message to the currently opened channel.
|
||||
/// </summary>
|
||||
/// <param name="text">The message text that is going to be posted</param>
|
||||
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
|
||||
/// <param name="target">An optional target channel. If null, <see cref="ChannelManager.CurrentChannel"/> will be used.</param>
|
||||
void PostMessage(string text, bool isAction = false, Channel target = null);
|
||||
}
|
||||
}
|
55
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal file
55
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class NowPlayingCommand : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IChannelPostTarget channelManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> currentBeatmap { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
string verb;
|
||||
BeatmapInfo beatmap;
|
||||
|
||||
switch (api.Activity.Value)
|
||||
{
|
||||
case UserActivity.SoloGame solo:
|
||||
verb = "playing";
|
||||
beatmap = solo.Beatmap;
|
||||
break;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
verb = "editing";
|
||||
beatmap = edit.Beatmap;
|
||||
break;
|
||||
|
||||
default:
|
||||
verb = "listening to";
|
||||
beatmap = currentBeatmap.Value.BeatmapInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[https://osu.ppy.sh/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
|
||||
|
||||
channelManager.PostMessage($"is {verb} {beatmapString}", true);
|
||||
Expire();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Online
|
||||
@ -11,7 +11,7 @@ namespace osu.Game.Online
|
||||
/// <summary>
|
||||
/// A component which requires a constant polling process.
|
||||
/// </summary>
|
||||
public abstract class PollingComponent : Component
|
||||
public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
|
||||
{
|
||||
private double? lastTimePolled;
|
||||
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game
|
||||
|
||||
private DirectOverlay direct;
|
||||
|
||||
private SocialOverlay social;
|
||||
private DashboardOverlay dashboard;
|
||||
|
||||
private UserProfileOverlay userProfile;
|
||||
|
||||
@ -611,7 +611,7 @@ namespace osu.Game
|
||||
|
||||
//overlay elements
|
||||
loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||
@ -670,7 +670,7 @@ namespace osu.Game
|
||||
}
|
||||
|
||||
// ensure only one of these overlays are open at once.
|
||||
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay, rankingsOverlay };
|
||||
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, direct, changelogOverlay, rankingsOverlay };
|
||||
|
||||
foreach (var overlay in singleDisplayOverlays)
|
||||
{
|
||||
@ -842,7 +842,7 @@ namespace osu.Game
|
||||
return true;
|
||||
|
||||
case GlobalAction.ToggleSocial:
|
||||
social.ToggleVisibility();
|
||||
dashboard.ToggleVisibility();
|
||||
return true;
|
||||
|
||||
case GlobalAction.ResetInputSettings:
|
||||
|
24
osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
Normal file
24
osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard
|
||||
{
|
||||
public class DashboardOverlayHeader : TabControlOverlayHeader<DashboardOverlayTabs>
|
||||
{
|
||||
protected override OverlayTitle CreateTitle() => new DashboardTitle();
|
||||
|
||||
private class DashboardTitle : OverlayTitle
|
||||
{
|
||||
public DashboardTitle()
|
||||
{
|
||||
Title = "dashboard";
|
||||
IconTexture = "Icons/changelog";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DashboardOverlayTabs
|
||||
{
|
||||
Friends
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Friends
|
||||
{
|
||||
public class FriendDisplay : CompositeDrawable
|
||||
public class FriendDisplay : OverlayView<List<User>>
|
||||
{
|
||||
private List<User> users = new List<User>();
|
||||
|
||||
@ -26,34 +26,29 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
set
|
||||
{
|
||||
users = value;
|
||||
|
||||
onlineStreamControl.Populate(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private GetFriendsRequest request;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private Drawable currentContent;
|
||||
|
||||
private readonly FriendOnlineStreamControl onlineStreamControl;
|
||||
private readonly Box background;
|
||||
private readonly Box controlBackground;
|
||||
private readonly UserListToolbar userListToolbar;
|
||||
private readonly Container itemsPlaceholder;
|
||||
private readonly LoadingLayer loading;
|
||||
private FriendOnlineStreamControl onlineStreamControl;
|
||||
private Box background;
|
||||
private Box controlBackground;
|
||||
private UserListToolbar userListToolbar;
|
||||
private Container itemsPlaceholder;
|
||||
private LoadingLayer loading;
|
||||
|
||||
public FriendDisplay()
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@ -134,11 +129,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
background.Colour = colourProvider.Background4;
|
||||
controlBackground.Colour = colourProvider.Background5;
|
||||
}
|
||||
@ -152,14 +143,11 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
||||
}
|
||||
|
||||
public void Fetch()
|
||||
{
|
||||
if (!api.IsLoggedIn)
|
||||
return;
|
||||
protected override APIRequest<List<User>> CreateRequest() => new GetFriendsRequest();
|
||||
|
||||
request = new GetFriendsRequest();
|
||||
request.Success += response => Schedule(() => Users = response);
|
||||
api.Queue(request);
|
||||
protected override void OnSuccess(List<User> response)
|
||||
{
|
||||
Users = response;
|
||||
}
|
||||
|
||||
private void recreatePanels()
|
||||
@ -258,9 +246,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
150
osu.Game/Overlays/DashboardOverlay.cs
Normal file
150
osu.Game/Overlays/DashboardOverlay.cs
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Dashboard;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class DashboardOverlay : FullscreenOverlay
|
||||
{
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private Box background;
|
||||
private Container content;
|
||||
private DashboardOverlayHeader header;
|
||||
private LoadingLayer loading;
|
||||
private OverlayScrollContainer scrollFlow;
|
||||
|
||||
public DashboardOverlay()
|
||||
: base(OverlayColourScheme.Purple)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
scrollFlow = new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
header = new DashboardOverlayHeader
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Depth = -float.MaxValue
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingLayer(content),
|
||||
};
|
||||
|
||||
background.Colour = ColourProvider.Background5;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
header.Current.BindValueChanged(onTabChanged);
|
||||
}
|
||||
|
||||
private bool displayUpdateRequired = true;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
// We don't want to create a new display on every call, only when exiting from fully closed state.
|
||||
if (displayUpdateRequired)
|
||||
{
|
||||
header.Current.TriggerChange();
|
||||
displayUpdateRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopOutComplete()
|
||||
{
|
||||
base.PopOutComplete();
|
||||
loadDisplay(Empty());
|
||||
displayUpdateRequired = true;
|
||||
}
|
||||
|
||||
private void loadDisplay(Drawable display)
|
||||
{
|
||||
scrollFlow.ScrollToStart();
|
||||
|
||||
LoadComponentAsync(display, loaded =>
|
||||
{
|
||||
if (API.IsLoggedIn)
|
||||
loading.Hide();
|
||||
|
||||
content.Child = loaded;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
private void onTabChanged(ValueChangedEvent<DashboardOverlayTabs> tab)
|
||||
{
|
||||
cancellationToken?.Cancel();
|
||||
loading.Show();
|
||||
|
||||
if (!API.IsLoggedIn)
|
||||
{
|
||||
loadDisplay(Empty());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab.NewValue)
|
||||
{
|
||||
case DashboardOverlayTabs.Friends:
|
||||
loadDisplay(new FriendDisplay());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
header.Current.TriggerChange();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class OverlayHeader : Container
|
||||
{
|
||||
public const int CONTENT_X_MARGIN = 50;
|
||||
|
||||
private readonly Box titleBackground;
|
||||
|
||||
protected readonly FillFlowContainer HeaderInfo;
|
||||
@ -54,7 +56,7 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
|
||||
Horizontal = CONTENT_X_MARGIN,
|
||||
},
|
||||
Children = new[]
|
||||
{
|
||||
|
79
osu.Game/Overlays/OverlayView.cs
Normal file
79
osu.Game/Overlays/OverlayView.cs
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
/// <summary>
|
||||
/// A subview containing online content, to be displayed inside a <see cref="FullscreenOverlay"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Automatically performs a data fetch on load.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the API response.</typeparam>
|
||||
public abstract class OverlayView<T> : CompositeDrawable, IOnlineComponent
|
||||
where T : class
|
||||
{
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
private APIRequest<T> request;
|
||||
|
||||
protected OverlayView()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
API.Register(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the API request for fetching data.
|
||||
/// </summary>
|
||||
protected abstract APIRequest<T> CreateRequest();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when results arrive from the main API request.
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
protected abstract void OnSuccess(T response);
|
||||
|
||||
/// <summary>
|
||||
/// Force a re-request for data from the API.
|
||||
/// </summary>
|
||||
protected void PerformFetch()
|
||||
{
|
||||
request?.Cancel();
|
||||
|
||||
request = CreateRequest();
|
||||
request.Success += response => Schedule(() => OnSuccess(response));
|
||||
|
||||
API.Queue(request);
|
||||
}
|
||||
|
||||
public virtual void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case APIState.Online:
|
||||
PerformFetch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
API?.Unregister(this);
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ namespace osu.Game.Overlays
|
||||
},
|
||||
TabControl = CreateTabControl().With(control =>
|
||||
{
|
||||
control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN };
|
||||
control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN };
|
||||
control.Current = Current;
|
||||
})
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Overlays.Toolbar
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(SocialOverlay chat)
|
||||
private void load(DashboardOverlay dashboard)
|
||||
{
|
||||
StateContainer = chat;
|
||||
StateContainer = dashboard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,13 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected Bindable<bool> IncreaseFirstObjectVisibility = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the provided hitobject should be considered the "first" hideable object.
|
||||
/// Can be used to skip spinners, for instance.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The hitobject to check.</param>
|
||||
protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true;
|
||||
|
||||
public void ReadFromConfig(OsuConfigManager config)
|
||||
{
|
||||
IncreaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
||||
@ -30,8 +37,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0))
|
||||
d.ApplyCustomUpdateState += ApplyHiddenState;
|
||||
if (IncreaseFirstObjectVisibility.Value)
|
||||
drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1);
|
||||
|
||||
foreach (var dho in drawables)
|
||||
dho.ApplyCustomUpdateState += ApplyHiddenState;
|
||||
}
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
|
@ -426,7 +426,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// selection has been changed as the result of a user interaction.
|
||||
/// Selection has been changed as the result of a user interaction.
|
||||
/// </summary>
|
||||
private void performUpdateSelected()
|
||||
{
|
||||
@ -435,7 +435,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
selectionChangedDebounce?.Cancel();
|
||||
|
||||
if (beatmap == null)
|
||||
if (beatmapNoDebounce == null)
|
||||
run();
|
||||
else
|
||||
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
||||
@ -448,9 +448,11 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
Mods.Value = Array.Empty<Mod>();
|
||||
|
||||
// required to return once in order to have the carousel in a good state.
|
||||
// if the ruleset changed, the rest of the selection update will happen via updateSelectedRuleset.
|
||||
return;
|
||||
// transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it.
|
||||
// The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here.
|
||||
// We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert).
|
||||
if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false))
|
||||
beatmap = null;
|
||||
}
|
||||
|
||||
// We may be arriving here due to another component changing the bindable Beatmap.
|
||||
@ -714,7 +716,7 @@ namespace osu.Game.Screens.Select
|
||||
if (decoupledRuleset.Value?.Equals(Ruleset.Value) == true)
|
||||
return false;
|
||||
|
||||
Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\"");
|
||||
Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\")");
|
||||
rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value;
|
||||
|
||||
// if we have a pending filter operation, we want to run it now.
|
||||
|
@ -44,6 +44,12 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// osu!catch section only has colour settings
|
||||
// so no harm in handling the entire section
|
||||
case Section.CatchTheBeat:
|
||||
HandleColours(skin, line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pair.Key))
|
||||
|
@ -19,11 +19,11 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="DiffPlex" Version="1.6.1" />
|
||||
<PackageReference Include="Humanizer" Version="2.7.9" />
|
||||
<PackageReference Include="Humanizer" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.411.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.417.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
||||
<PackageReference Include="Sentry" Version="2.1.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||
|
@ -70,17 +70,17 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.411.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.417.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
||||
</ItemGroup>
|
||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<PackageReference Include="DiffPlex" Version="1.6.1" />
|
||||
<PackageReference Include="Humanizer" Version="2.7.9" />
|
||||
<PackageReference Include="Humanizer" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.411.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.417.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user