mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 09:22:54 +08:00
Merge branch 'master' into allow-cancelling-completion
This commit is contained in:
commit
5a2129da7c
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
{
|
{
|
||||||
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
||||||
|
|
||||||
if (TargetColumns >= 10)
|
if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
|
||||||
{
|
{
|
||||||
TargetColumns /= 2;
|
TargetColumns /= 2;
|
||||||
Dual = true;
|
Dual = true;
|
||||||
|
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class DualStageVariantGenerator
|
||||||
|
{
|
||||||
|
private readonly int singleStageVariant;
|
||||||
|
private readonly InputKey[] stage1LeftKeys;
|
||||||
|
private readonly InputKey[] stage1RightKeys;
|
||||||
|
private readonly InputKey[] stage2LeftKeys;
|
||||||
|
private readonly InputKey[] stage2RightKeys;
|
||||||
|
|
||||||
|
public DualStageVariantGenerator(int singleStageVariant)
|
||||||
|
{
|
||||||
|
this.singleStageVariant = singleStageVariant;
|
||||||
|
|
||||||
|
// 10K is special because it expands towards the centre of the keyboard (VM/BN), rather than towards the edges of the keyboard.
|
||||||
|
if (singleStageVariant == 10)
|
||||||
|
{
|
||||||
|
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R, InputKey.V };
|
||||||
|
stage1RightKeys = new[] { InputKey.M, InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||||
|
|
||||||
|
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G, InputKey.B };
|
||||||
|
stage2RightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R };
|
||||||
|
stage1RightKeys = new[] { InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||||
|
|
||||||
|
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G };
|
||||||
|
stage2RightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> GenerateMappings()
|
||||||
|
{
|
||||||
|
var stage1Bindings = new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = stage1LeftKeys,
|
||||||
|
RightKeys = stage1RightKeys,
|
||||||
|
SpecialKey = InputKey.V,
|
||||||
|
SpecialAction = ManiaAction.Special1,
|
||||||
|
NormalActionStart = ManiaAction.Key1
|
||||||
|
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
||||||
|
|
||||||
|
var stage2Bindings = new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = stage2LeftKeys,
|
||||||
|
RightKeys = stage2RightKeys,
|
||||||
|
SpecialKey = InputKey.B,
|
||||||
|
SpecialAction = ManiaAction.Special2,
|
||||||
|
NormalActionStart = nextNormal
|
||||||
|
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
||||||
|
|
||||||
|
return stage1Bindings.Concat(stage2Bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,5 +78,11 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
[Description("Key 18")]
|
[Description("Key 18")]
|
||||||
Key18,
|
Key18,
|
||||||
|
|
||||||
|
[Description("Key 19")]
|
||||||
|
Key19,
|
||||||
|
|
||||||
|
[Description("Key 20")]
|
||||||
|
Key20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of supported keys in a single stage.
|
||||||
|
/// </summary>
|
||||||
|
public const int MAX_STAGE_KEYS = 10;
|
||||||
|
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
||||||
@ -202,6 +207,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModKey7(),
|
new ManiaModKey7(),
|
||||||
new ManiaModKey8(),
|
new ManiaModKey8(),
|
||||||
new ManiaModKey9(),
|
new ManiaModKey9(),
|
||||||
|
new ManiaModKey10(),
|
||||||
new ManiaModKey1(),
|
new ManiaModKey1(),
|
||||||
new ManiaModKey2(),
|
new ManiaModKey2(),
|
||||||
new ManiaModKey3()),
|
new ManiaModKey3()),
|
||||||
@ -250,9 +256,9 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
for (int i = 1; i <= 9; i++)
|
for (int i = 1; i <= MAX_STAGE_KEYS; i++)
|
||||||
yield return (int)PlayfieldType.Single + i;
|
yield return (int)PlayfieldType.Single + i;
|
||||||
for (int i = 2; i <= 18; i += 2)
|
for (int i = 2; i <= MAX_STAGE_KEYS * 2; i += 2)
|
||||||
yield return (int)PlayfieldType.Dual + i;
|
yield return (int)PlayfieldType.Dual + i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,73 +268,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
switch (getPlayfieldType(variant))
|
switch (getPlayfieldType(variant))
|
||||||
{
|
{
|
||||||
case PlayfieldType.Single:
|
case PlayfieldType.Single:
|
||||||
return new VariantMappingGenerator
|
return new SingleStageVariantGenerator(variant).GenerateMappings();
|
||||||
{
|
|
||||||
LeftKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.A,
|
|
||||||
InputKey.S,
|
|
||||||
InputKey.D,
|
|
||||||
InputKey.F
|
|
||||||
},
|
|
||||||
RightKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.J,
|
|
||||||
InputKey.K,
|
|
||||||
InputKey.L,
|
|
||||||
InputKey.Semicolon
|
|
||||||
},
|
|
||||||
SpecialKey = InputKey.Space,
|
|
||||||
SpecialAction = ManiaAction.Special1,
|
|
||||||
NormalActionStart = ManiaAction.Key1,
|
|
||||||
}.GenerateKeyBindingsFor(variant, out _);
|
|
||||||
|
|
||||||
case PlayfieldType.Dual:
|
case PlayfieldType.Dual:
|
||||||
int keys = getDualStageKeyCount(variant);
|
return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings();
|
||||||
|
|
||||||
var stage1Bindings = new VariantMappingGenerator
|
|
||||||
{
|
|
||||||
LeftKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.Q,
|
|
||||||
InputKey.W,
|
|
||||||
InputKey.E,
|
|
||||||
InputKey.R,
|
|
||||||
},
|
|
||||||
RightKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.X,
|
|
||||||
InputKey.C,
|
|
||||||
InputKey.V,
|
|
||||||
InputKey.B
|
|
||||||
},
|
|
||||||
SpecialKey = InputKey.S,
|
|
||||||
SpecialAction = ManiaAction.Special1,
|
|
||||||
NormalActionStart = ManiaAction.Key1
|
|
||||||
}.GenerateKeyBindingsFor(keys, out var nextNormal);
|
|
||||||
|
|
||||||
var stage2Bindings = new VariantMappingGenerator
|
|
||||||
{
|
|
||||||
LeftKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.Number7,
|
|
||||||
InputKey.Number8,
|
|
||||||
InputKey.Number9,
|
|
||||||
InputKey.Number0
|
|
||||||
},
|
|
||||||
RightKeys = new[]
|
|
||||||
{
|
|
||||||
InputKey.K,
|
|
||||||
InputKey.L,
|
|
||||||
InputKey.Semicolon,
|
|
||||||
InputKey.Quote
|
|
||||||
},
|
|
||||||
SpecialKey = InputKey.I,
|
|
||||||
SpecialAction = ManiaAction.Special2,
|
|
||||||
NormalActionStart = nextNormal
|
|
||||||
}.GenerateKeyBindingsFor(keys, out _);
|
|
||||||
|
|
||||||
return stage1Bindings.Concat(stage2Bindings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.Empty<KeyBinding>();
|
return Array.Empty<KeyBinding>();
|
||||||
@ -364,59 +307,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VariantMappingGenerator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// All the <see cref="InputKey"/>s available to the left hand.
|
|
||||||
/// </summary>
|
|
||||||
public InputKey[] LeftKeys;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All the <see cref="InputKey"/>s available to the right hand.
|
|
||||||
/// </summary>
|
|
||||||
public InputKey[] RightKeys;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="InputKey"/> for the special key.
|
|
||||||
/// </summary>
|
|
||||||
public InputKey SpecialKey;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
|
||||||
/// </summary>
|
|
||||||
public ManiaAction NormalActionStart;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ManiaAction"/> for the special column.
|
|
||||||
/// </summary>
|
|
||||||
public ManiaAction SpecialAction;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
|
||||||
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
|
||||||
/// <returns>The keybindings.</returns>
|
|
||||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
|
||||||
{
|
|
||||||
ManiaAction currentNormalAction = NormalActionStart;
|
|
||||||
|
|
||||||
var bindings = new List<KeyBinding>();
|
|
||||||
|
|
||||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
|
||||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
|
||||||
|
|
||||||
if (columns % 2 == 1)
|
|
||||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
|
||||||
|
|
||||||
for (int i = 0; i < columns / 2; i++)
|
|
||||||
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
|
||||||
|
|
||||||
nextNormalAction = currentNormalAction;
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PlayfieldType
|
public enum PlayfieldType
|
||||||
|
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModKey10 : ManiaKeyMod
|
||||||
|
{
|
||||||
|
public override int KeyCount => 10;
|
||||||
|
public override string Name => "Ten Keys";
|
||||||
|
public override string Acronym => "10K";
|
||||||
|
public override string Description => @"Play with ten keys.";
|
||||||
|
}
|
||||||
|
}
|
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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 osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class SingleStageVariantGenerator
|
||||||
|
{
|
||||||
|
private readonly int variant;
|
||||||
|
private readonly InputKey[] leftKeys;
|
||||||
|
private readonly InputKey[] rightKeys;
|
||||||
|
|
||||||
|
public SingleStageVariantGenerator(int variant)
|
||||||
|
{
|
||||||
|
this.variant = variant;
|
||||||
|
|
||||||
|
// 10K is special because it expands towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard.
|
||||||
|
if (variant == 10)
|
||||||
|
{
|
||||||
|
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F, InputKey.V };
|
||||||
|
rightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F };
|
||||||
|
rightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> GenerateMappings() => new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = leftKeys,
|
||||||
|
RightKeys = rightKeys,
|
||||||
|
SpecialKey = InputKey.Space,
|
||||||
|
SpecialAction = ManiaAction.Special1,
|
||||||
|
NormalActionStart = ManiaAction.Key1,
|
||||||
|
}.GenerateKeyBindingsFor(variant, out _);
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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 osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class VariantMappingGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All the <see cref="InputKey"/>s available to the left hand.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey[] LeftKeys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All the <see cref="InputKey"/>s available to the right hand.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey[] RightKeys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="InputKey"/> for the special key.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey SpecialKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||||
|
/// </summary>
|
||||||
|
public ManiaAction NormalActionStart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ManiaAction"/> for the special column.
|
||||||
|
/// </summary>
|
||||||
|
public ManiaAction SpecialAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||||
|
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||||
|
/// <returns>The keybindings.</returns>
|
||||||
|
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||||
|
{
|
||||||
|
ManiaAction currentNormalAction = NormalActionStart;
|
||||||
|
|
||||||
|
var bindings = new List<KeyBinding>();
|
||||||
|
|
||||||
|
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||||
|
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||||
|
|
||||||
|
if (columns % 2 == 1)
|
||||||
|
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||||
|
|
||||||
|
for (int i = 0; i < columns / 2; i++)
|
||||||
|
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||||
|
|
||||||
|
nextNormalAction = currentNormalAction;
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
private const double fade_in_duration_multiplier = 0.4;
|
private const double fade_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
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)
|
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||||
throw new InvalidOperationException($"A {hitObject} was hit before it become hittable!");
|
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||||
|
|
||||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
{
|
{
|
||||||
|
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.Game.Overlays;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
private FriendDisplay display;
|
private TestFriendDisplay display;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Child = new BasicScrollContainer
|
Child = new BasicScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = display = new FriendDisplay()
|
Child = display = new TestFriendDisplay()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
LastVisit = DateTimeOffset.Now
|
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);
|
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]
|
[Test]
|
||||||
public void TestRulesetChangeResetsMods()
|
public void TestRulesetChangeResetsMods()
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
typeof(OnScreenDisplay),
|
typeof(OnScreenDisplay),
|
||||||
typeof(NotificationOverlay),
|
typeof(NotificationOverlay),
|
||||||
typeof(DirectOverlay),
|
typeof(DirectOverlay),
|
||||||
typeof(SocialOverlay),
|
typeof(DashboardOverlay),
|
||||||
typeof(ChannelManager),
|
typeof(ChannelManager),
|
||||||
typeof(ChatOverlay),
|
typeof(ChatOverlay),
|
||||||
typeof(SettingsOverlay),
|
typeof(SettingsOverlay),
|
||||||
|
@ -187,27 +187,13 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
writer.WriteLine("[HitObjects]");
|
writer.WriteLine("[HitObjects]");
|
||||||
|
|
||||||
|
// TODO: implement other legacy rulesets
|
||||||
switch (beatmap.BeatmapInfo.RulesetID)
|
switch (beatmap.BeatmapInfo.RulesetID)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
foreach (var h in beatmap.HitObjects)
|
foreach (var h in beatmap.HitObjects)
|
||||||
handleOsuHitObject(writer, h);
|
handleOsuHitObject(writer, h);
|
||||||
break;
|
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)
|
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||||
{
|
{
|
||||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||||
|
@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
|
|||||||
store.KeyBindingChanged -= ReloadMappings;
|
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; }
|
public T Result { get; private set; }
|
||||||
|
|
||||||
protected APIRequest()
|
|
||||||
{
|
|
||||||
base.Success += () => TriggerSuccess(((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked on successful completion of an API request.
|
/// Invoked on successful completion of an API request.
|
||||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T> Success;
|
||||||
|
|
||||||
|
protected override void PostProcess()
|
||||||
|
{
|
||||||
|
base.PostProcess();
|
||||||
|
Result = ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
|
||||||
|
}
|
||||||
|
|
||||||
internal void TriggerSuccess(T result)
|
internal void TriggerSuccess(T result)
|
||||||
{
|
{
|
||||||
if (Result != null)
|
if (Result != null)
|
||||||
throw new InvalidOperationException("Attempted to trigger success more than once");
|
throw new InvalidOperationException("Attempted to trigger success more than once");
|
||||||
|
|
||||||
Result = result;
|
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())
|
if (checkAndScheduleFailure())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
PostProcess();
|
||||||
|
|
||||||
API.Schedule(delegate
|
API.Schedule(delegate
|
||||||
{
|
{
|
||||||
if (cancelled) return;
|
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();
|
Success?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages everything channel related
|
/// Manages everything channel related
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChannelManager : PollingComponent
|
public class ChannelManager : PollingComponent, IChannelPostTarget
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The channels the player joins on startup
|
/// The channels the player joins on startup
|
||||||
@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
|
case "np":
|
||||||
|
AddInternal(new NowPlayingCommand());
|
||||||
|
break;
|
||||||
|
|
||||||
case "me":
|
case "me":
|
||||||
if (string.IsNullOrWhiteSpace(content))
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
{
|
{
|
||||||
@ -234,7 +238,7 @@ namespace osu.Game.Online.Chat
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "help":
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
namespace osu.Game.Online
|
namespace osu.Game.Online
|
||||||
@ -11,7 +11,7 @@ namespace osu.Game.Online
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component which requires a constant polling process.
|
/// A component which requires a constant polling process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PollingComponent : Component
|
public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
|
||||||
{
|
{
|
||||||
private double? lastTimePolled;
|
private double? lastTimePolled;
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
private DirectOverlay direct;
|
private DirectOverlay direct;
|
||||||
|
|
||||||
private SocialOverlay social;
|
private DashboardOverlay dashboard;
|
||||||
|
|
||||||
private UserProfileOverlay userProfile;
|
private UserProfileOverlay userProfile;
|
||||||
|
|
||||||
@ -611,7 +611,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
//overlay elements
|
//overlay elements
|
||||||
loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true);
|
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);
|
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, 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.
|
// 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)
|
foreach (var overlay in singleDisplayOverlays)
|
||||||
{
|
{
|
||||||
@ -842,7 +842,7 @@ namespace osu.Game
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ToggleSocial:
|
case GlobalAction.ToggleSocial:
|
||||||
social.ToggleVisibility();
|
dashboard.ToggleVisibility();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.ResetInputSettings:
|
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
|
namespace osu.Game.Overlays.Dashboard.Friends
|
||||||
{
|
{
|
||||||
public class FriendDisplay : CompositeDrawable
|
public class FriendDisplay : OverlayView<List<User>>
|
||||||
{
|
{
|
||||||
private List<User> users = new List<User>();
|
private List<User> users = new List<User>();
|
||||||
|
|
||||||
@ -26,34 +26,29 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
users = value;
|
users = value;
|
||||||
|
|
||||||
onlineStreamControl.Populate(value);
|
onlineStreamControl.Populate(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
private GetFriendsRequest request;
|
|
||||||
private CancellationTokenSource cancellationToken;
|
private CancellationTokenSource cancellationToken;
|
||||||
|
|
||||||
private Drawable currentContent;
|
private Drawable currentContent;
|
||||||
|
|
||||||
private readonly FriendOnlineStreamControl onlineStreamControl;
|
private FriendOnlineStreamControl onlineStreamControl;
|
||||||
private readonly Box background;
|
private Box background;
|
||||||
private readonly Box controlBackground;
|
private Box controlBackground;
|
||||||
private readonly UserListToolbar userListToolbar;
|
private UserListToolbar userListToolbar;
|
||||||
private readonly Container itemsPlaceholder;
|
private Container itemsPlaceholder;
|
||||||
private readonly LoadingLayer loading;
|
private LoadingLayer loading;
|
||||||
|
|
||||||
public FriendDisplay()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
InternalChild = new FillFlowContainer
|
InternalChild = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
@ -134,11 +129,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
background.Colour = colourProvider.Background4;
|
background.Colour = colourProvider.Background4;
|
||||||
controlBackground.Colour = colourProvider.Background5;
|
controlBackground.Colour = colourProvider.Background5;
|
||||||
}
|
}
|
||||||
@ -152,14 +143,11 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Fetch()
|
protected override APIRequest<List<User>> CreateRequest() => new GetFriendsRequest();
|
||||||
{
|
|
||||||
if (!api.IsLoggedIn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
request = new GetFriendsRequest();
|
protected override void OnSuccess(List<User> response)
|
||||||
request.Success += response => Schedule(() => Users = response);
|
{
|
||||||
api.Queue(request);
|
Users = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recreatePanels()
|
private void recreatePanels()
|
||||||
@ -258,9 +246,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
request?.Cancel();
|
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
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 abstract class OverlayHeader : Container
|
||||||
{
|
{
|
||||||
|
public const int CONTENT_X_MARGIN = 50;
|
||||||
|
|
||||||
private readonly Box titleBackground;
|
private readonly Box titleBackground;
|
||||||
|
|
||||||
protected readonly FillFlowContainer HeaderInfo;
|
protected readonly FillFlowContainer HeaderInfo;
|
||||||
@ -54,7 +56,7 @@ namespace osu.Game.Overlays
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
|
Horizontal = CONTENT_X_MARGIN,
|
||||||
},
|
},
|
||||||
Children = new[]
|
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 =>
|
TabControl = CreateTabControl().With(control =>
|
||||||
{
|
{
|
||||||
control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN };
|
control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN };
|
||||||
control.Current = Current;
|
control.Current = Current;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[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>();
|
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)
|
public void ReadFromConfig(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
IncreaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
IncreaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
||||||
@ -30,8 +37,11 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0))
|
if (IncreaseFirstObjectVisibility.Value)
|
||||||
d.ApplyCustomUpdateState += ApplyHiddenState;
|
drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1);
|
||||||
|
|
||||||
|
foreach (var dho in drawables)
|
||||||
|
dho.ApplyCustomUpdateState += ApplyHiddenState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
@ -28,8 +28,15 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private const float bleed_top = FilterControl.HEIGHT;
|
/// <summary>
|
||||||
private const float bleed_bottom = Footer.HEIGHT;
|
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||||
|
/// </summary>
|
||||||
|
public float BleedTop { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||||
|
/// </summary>
|
||||||
|
public float BleedBottom { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Triggered when the <see cref="BeatmapSets"/> loaded change and are completely loaded.
|
/// Triggered when the <see cref="BeatmapSets"/> loaded change and are completely loaded.
|
||||||
@ -373,17 +380,17 @@ namespace osu.Game.Screens.Select
|
|||||||
/// the beatmap carousel bleeds into the <see cref="FilterControl"/> and the <see cref="Footer"/>
|
/// the beatmap carousel bleeds into the <see cref="FilterControl"/> and the <see cref="Footer"/>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2;
|
private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position of the lower visible bound with respect to the current scroll position.
|
/// The position of the lower visible bound with respect to the current scroll position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom;
|
private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position of the upper visible bound with respect to the current scroll position.
|
/// The position of the upper visible bound with respect to the current scroll position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private float visibleUpperBound => scroll.Current - bleed_top;
|
private float visibleUpperBound => scroll.Current - BleedTop;
|
||||||
|
|
||||||
public void FlushPendingFilterOperations()
|
public void FlushPendingFilterOperations()
|
||||||
{
|
{
|
||||||
@ -641,7 +648,11 @@ namespace osu.Game.Screens.Select
|
|||||||
case DrawableCarouselBeatmap beatmap:
|
case DrawableCarouselBeatmap beatmap:
|
||||||
{
|
{
|
||||||
if (beatmap.Item.State.Value == CarouselItemState.Selected)
|
if (beatmap.Item.State.Value == CarouselItemState.Selected)
|
||||||
scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2;
|
// scroll position at currentY makes the set panel appear at the very top of the carousel's screen space
|
||||||
|
// move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas)
|
||||||
|
// then reapply the top semi-transparent area (because carousel's screen space starts below it)
|
||||||
|
// and finally add half of the panel's own height to achieve vertical centering of the panel itself
|
||||||
|
scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2;
|
||||||
|
|
||||||
void performMove(float y, float? startY = null)
|
void performMove(float y, float? startY = null)
|
||||||
{
|
{
|
||||||
|
@ -153,6 +153,8 @@ namespace osu.Game.Screens.Select
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
BleedTop = FilterControl.HEIGHT,
|
||||||
|
BleedBottom = Footer.HEIGHT,
|
||||||
SelectionChanged = updateSelectedBeatmap,
|
SelectionChanged = updateSelectedBeatmap,
|
||||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||||
GetRecommendedBeatmap = recommender.GetRecommendedBeatmap,
|
GetRecommendedBeatmap = recommender.GetRecommendedBeatmap,
|
||||||
@ -426,7 +428,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// selection has been changed as the result of a user interaction.
|
/// Selection has been changed as the result of a user interaction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void performUpdateSelected()
|
private void performUpdateSelected()
|
||||||
{
|
{
|
||||||
@ -435,7 +437,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
selectionChangedDebounce?.Cancel();
|
selectionChangedDebounce?.Cancel();
|
||||||
|
|
||||||
if (beatmap == null)
|
if (beatmapNoDebounce == null)
|
||||||
run();
|
run();
|
||||||
else
|
else
|
||||||
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
||||||
@ -448,9 +450,11 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Mods.Value = Array.Empty<Mod>();
|
Mods.Value = Array.Empty<Mod>();
|
||||||
|
|
||||||
// required to return once in order to have the carousel in a good state.
|
// transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it.
|
||||||
// if the ruleset changed, the rest of the selection update will happen via updateSelectedRuleset.
|
// The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here.
|
||||||
return;
|
// 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.
|
// We may be arriving here due to another component changing the bindable Beatmap.
|
||||||
@ -714,7 +718,7 @@ namespace osu.Game.Screens.Select
|
|||||||
if (decoupledRuleset.Value?.Equals(Ruleset.Value) == true)
|
if (decoupledRuleset.Value?.Equals(Ruleset.Value) == true)
|
||||||
return false;
|
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;
|
rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value;
|
||||||
|
|
||||||
// if we have a pending filter operation, we want to run it now.
|
// if we have a pending filter operation, we want to run it now.
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="DiffPlex" Version="1.6.1" />
|
<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" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<PackageReference Include="DiffPlex" Version="1.6.1" />
|
<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" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
Loading…
Reference in New Issue
Block a user