mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 11:47:24 +08:00
Merge branch 'master' into ruleset-selection-duck-tweak
This commit is contained in:
commit
366c0f664f
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
|||||||
# Attempt to upload results even if test fails.
|
# Attempt to upload results even if test fails.
|
||||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1025.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -5,7 +5,6 @@ using Android.Content.PM;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game;
|
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
@ -28,7 +27,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
gameActivity.RunOnUiThread(() =>
|
gameActivity.RunOnUiThread(() =>
|
||||||
{
|
{
|
||||||
gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingState.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
gameActivity.RequestedOrientation = userPlaying.NewValue == LocalUserPlayingState.Playing ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
public partial class CatchEditorPlayfield : CatchPlayfield
|
public partial class CatchEditorPlayfield : CatchPlayfield
|
||||||
{
|
{
|
||||||
// TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen.
|
|
||||||
public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty)
|
public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty)
|
||||||
: base(difficulty)
|
: base(difficulty)
|
||||||
{
|
{
|
||||||
|
@ -2,16 +2,22 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public partial class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
public partial class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
|
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
|
||||||
|
|
||||||
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||||
@ -28,6 +34,30 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
|
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
editorBeatmap.BeatmapReprocessed += onBeatmapReprocessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editorBeatmap.IsNotNull())
|
||||||
|
editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBeatmapReprocessed()
|
||||||
|
{
|
||||||
|
if (Playfield is CatchEditorPlayfield catchPlayfield)
|
||||||
|
{
|
||||||
|
catchPlayfield.Catcher.ApplyDifficulty(editorBeatmap.Difficulty);
|
||||||
|
catchPlayfield.CatcherArea.CatcherTrails.UpdateCatcherTrailsScale(catchPlayfield.Catcher.BodyScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float CatchWidth;
|
public float CatchWidth { get; private set; }
|
||||||
|
|
||||||
private readonly SkinnableCatcher body;
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
@ -142,10 +142,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
Size = new Vector2(BASE_SIZE);
|
Size = new Vector2(BASE_SIZE);
|
||||||
|
|
||||||
if (difficulty != null)
|
ApplyDifficulty(difficulty);
|
||||||
Scale = calculateScale(difficulty);
|
|
||||||
|
|
||||||
CatchWidth = CalculateCatchWidth(Scale);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -312,6 +309,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the scale and catch width.
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyDifficulty(IBeatmapDifficultyInfo? difficulty)
|
||||||
|
{
|
||||||
|
if (difficulty != null)
|
||||||
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
|
CatchWidth = CalculateCatchWidth(Scale);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop any fruit off the plate.
|
/// Drop any fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
private readonly CatcherTrailDisplay catcherTrails;
|
public readonly CatcherTrailDisplay CatcherTrails;
|
||||||
|
|
||||||
private Catcher catcher = null!;
|
private Catcher catcher = null!;
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
catcherContainer = new Container<Catcher> { RelativeSizeAxes = Axes.Both },
|
catcherContainer = new Container<Catcher> { RelativeSizeAxes = Axes.Both },
|
||||||
catcherTrails = new CatcherTrailDisplay(),
|
CatcherTrails = new CatcherTrailDisplay(),
|
||||||
comboDisplay = new CatchComboDisplay
|
comboDisplay = new CatchComboDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
const double trail_generation_interval = 16;
|
const double trail_generation_interval = 16;
|
||||||
|
|
||||||
if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval)
|
if (Time.Current - CatcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||||
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +170,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
|
private void displayCatcherTrail(CatcherTrailAnimation animation) => CatcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Pooling;
|
|||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
@ -55,6 +57,25 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the scale of all trails.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scale">The new body scale of the Catcher</param>
|
||||||
|
public void UpdateCatcherTrailsScale(Vector2 scale)
|
||||||
|
{
|
||||||
|
var oldEntries = Entries.ToList();
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
foreach (var oldEntry in oldEntries)
|
||||||
|
{
|
||||||
|
// use magnitude of the new scale while preserving the sign of the old one in the X direction.
|
||||||
|
// the end effect is preserving the direction in which the trail sprites face, which is important.
|
||||||
|
var targetScale = new Vector2(Math.Abs(scale.X) * Math.Sign(oldEntry.Scale.X), Math.Abs(scale.Y));
|
||||||
|
Add(new CatcherTrailEntry(oldEntry.LifetimeStart, oldEntry.CatcherState, oldEntry.Position, targetScale, oldEntry.Animation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit);
|
countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit);
|
||||||
countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
|
countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
|
||||||
|
effectiveMissCount = countMiss;
|
||||||
|
|
||||||
if (osuAttributes.SliderCount > 0)
|
if (osuAttributes.SliderCount > 0)
|
||||||
{
|
{
|
||||||
@ -87,6 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
}
|
}
|
||||||
|
|
||||||
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
||||||
|
effectiveMissCount = Math.Min(totalHits, effectiveMissCount);
|
||||||
|
|
||||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 220,
|
Width = 220,
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -25,13 +26,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private readonly OsuGridToolboxGroup gridToolbox;
|
private readonly OsuGridToolboxGroup gridToolbox;
|
||||||
|
|
||||||
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, RotationOrigin.GridCentre));
|
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, EditorOrigin.GridCentre));
|
||||||
|
|
||||||
private SliderWithTextBoxInput<float> angleInput = null!;
|
private SliderWithTextBoxInput<float> angleInput = null!;
|
||||||
private EditorRadioButtonCollection rotationOrigin = null!;
|
private EditorRadioButtonCollection rotationOrigin = null!;
|
||||||
|
|
||||||
|
private RadioButton gridCentreButton = null!;
|
||||||
|
private RadioButton playfieldCentreButton = null!;
|
||||||
private RadioButton selectionCentreButton = null!;
|
private RadioButton selectionCentreButton = null!;
|
||||||
|
|
||||||
|
private Bindable<EditorOrigin> configRotationOrigin = null!;
|
||||||
|
|
||||||
public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox)
|
public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox)
|
||||||
{
|
{
|
||||||
this.rotationHandler = rotationHandler;
|
this.rotationHandler = rotationHandler;
|
||||||
@ -41,8 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
configRotationOrigin = config.GetBindable<EditorOrigin>(OsuSetting.EditorRotationOrigin);
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 220,
|
Width = 220,
|
||||||
@ -66,14 +73,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
new RadioButton("Grid centre",
|
gridCentreButton = new RadioButton("Grid centre",
|
||||||
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre },
|
() => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.GridCentre },
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
||||||
new RadioButton("Playfield centre",
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
||||||
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre },
|
() => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.PlayfieldCentre },
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||||
selectionCentreButton = new RadioButton("Selection centre",
|
selectionCentreButton = new RadioButton("Selection centre",
|
||||||
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre },
|
() => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.SelectionCentre },
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,13 +102,63 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
angleInput.SelectAll();
|
angleInput.SelectAll();
|
||||||
});
|
});
|
||||||
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
||||||
rotationOrigin.Items.First().Select();
|
|
||||||
|
|
||||||
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
||||||
{
|
{
|
||||||
selectionCentreButton.Selected.Disabled = !e.NewValue;
|
selectionCentreButton.Selected.Disabled = !e.NewValue;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
bool didSelect = false;
|
||||||
|
|
||||||
|
configRotationOrigin.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
switch (configRotationOrigin.Value)
|
||||||
|
{
|
||||||
|
case EditorOrigin.GridCentre:
|
||||||
|
if (!gridCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
gridCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EditorOrigin.PlayfieldCentre:
|
||||||
|
if (!playfieldCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
playfieldCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EditorOrigin.SelectionCentre:
|
||||||
|
if (!selectionCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
selectionCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
if (!didSelect)
|
||||||
|
rotationOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
||||||
|
|
||||||
|
gridCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configRotationOrigin.Value = EditorOrigin.GridCentre;
|
||||||
|
});
|
||||||
|
playfieldCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configRotationOrigin.Value = EditorOrigin.PlayfieldCentre;
|
||||||
|
});
|
||||||
|
selectionCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configRotationOrigin.Value = EditorOrigin.SelectionCentre;
|
||||||
|
});
|
||||||
|
|
||||||
rotationInfo.BindValueChanged(rotation =>
|
rotationInfo.BindValueChanged(rotation =>
|
||||||
{
|
{
|
||||||
rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue));
|
rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue));
|
||||||
@ -111,9 +168,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private Vector2? getOriginPosition(PreciseRotationInfo rotation) =>
|
private Vector2? getOriginPosition(PreciseRotationInfo rotation) =>
|
||||||
rotation.Origin switch
|
rotation.Origin switch
|
||||||
{
|
{
|
||||||
RotationOrigin.GridCentre => gridToolbox.StartPosition.Value,
|
EditorOrigin.GridCentre => gridToolbox.StartPosition.Value,
|
||||||
RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2,
|
EditorOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2,
|
||||||
RotationOrigin.SelectionCentre => null,
|
EditorOrigin.SelectionCentre => null,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(rotation))
|
_ => throw new ArgumentOutOfRangeException(nameof(rotation))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,12 +200,5 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RotationOrigin
|
public record PreciseRotationInfo(float Degrees, EditorOrigin Origin);
|
||||||
{
|
|
||||||
GridCentre,
|
|
||||||
PlayfieldCentre,
|
|
||||||
SelectionCentre
|
|
||||||
}
|
|
||||||
|
|
||||||
public record PreciseRotationInfo(float Degrees, RotationOrigin Origin);
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -18,6 +19,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
@ -28,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private readonly OsuGridToolboxGroup gridToolbox;
|
private readonly OsuGridToolboxGroup gridToolbox;
|
||||||
|
|
||||||
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true));
|
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
|
||||||
|
|
||||||
private SliderWithTextBoxInput<float> scaleInput = null!;
|
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||||
private BindableNumber<float> scaleInputBindable = null!;
|
private BindableNumber<float> scaleInputBindable = null!;
|
||||||
@ -41,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private OsuCheckbox xCheckBox = null!;
|
private OsuCheckbox xCheckBox = null!;
|
||||||
private OsuCheckbox yCheckBox = null!;
|
private OsuCheckbox yCheckBox = null!;
|
||||||
|
|
||||||
|
private Bindable<EditorOrigin> configScaleOrigin = null!;
|
||||||
|
|
||||||
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
||||||
|
|
||||||
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
|
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
|
||||||
@ -52,10 +56,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(EditorBeatmap editorBeatmap)
|
private void load(EditorBeatmap editorBeatmap, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
||||||
|
|
||||||
|
configScaleOrigin = config.GetBindable<EditorOrigin>(OsuSetting.EditorScaleOrigin);
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 220,
|
Width = 220,
|
||||||
@ -67,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
Current = scaleInputBindable = new BindableNumber<float>
|
Current = scaleInputBindable = new BindableNumber<float>
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.05f,
|
||||||
MaxValue = 2,
|
MaxValue = 2,
|
||||||
Precision = 0.001f,
|
Precision = 0.001f,
|
||||||
Value = 1,
|
Value = 1,
|
||||||
@ -82,13 +88,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
gridCentreButton = new RadioButton("Grid centre",
|
gridCentreButton = new RadioButton("Grid centre",
|
||||||
() => setOrigin(ScaleOrigin.GridCentre),
|
() => setOrigin(EditorOrigin.GridCentre),
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
||||||
playfieldCentreButton = new RadioButton("Playfield centre",
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
||||||
() => setOrigin(ScaleOrigin.PlayfieldCentre),
|
() => setOrigin(EditorOrigin.PlayfieldCentre),
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||||
selectionCentreButton = new RadioButton("Selection centre",
|
selectionCentreButton = new RadioButton("Selection centre",
|
||||||
() => setOrigin(ScaleOrigin.SelectionCentre),
|
() => setOrigin(EditorOrigin.SelectionCentre),
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -165,7 +171,56 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
||||||
gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
|
gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
|
||||||
|
|
||||||
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
bool didSelect = false;
|
||||||
|
|
||||||
|
configScaleOrigin.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
switch (configScaleOrigin.Value)
|
||||||
|
{
|
||||||
|
case EditorOrigin.GridCentre:
|
||||||
|
if (!gridCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
gridCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EditorOrigin.PlayfieldCentre:
|
||||||
|
if (!playfieldCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
playfieldCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EditorOrigin.SelectionCentre:
|
||||||
|
if (!selectionCentreButton.Selected.Disabled)
|
||||||
|
{
|
||||||
|
selectionCentreButton.Select();
|
||||||
|
didSelect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
if (!didSelect)
|
||||||
|
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
||||||
|
|
||||||
|
gridCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.GridCentre;
|
||||||
|
});
|
||||||
|
playfieldCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.PlayfieldCentre;
|
||||||
|
});
|
||||||
|
selectionCentreButton.Selected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.SelectionCentre;
|
||||||
|
});
|
||||||
|
|
||||||
scaleInfo.BindValueChanged(scale =>
|
scaleInfo.BindValueChanged(scale =>
|
||||||
{
|
{
|
||||||
@ -182,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private void updateAxisCheckBoxesEnabled()
|
private void updateAxisCheckBoxesEnabled()
|
||||||
{
|
{
|
||||||
if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre)
|
if (scaleInfo.Value.Origin != EditorOrigin.SelectionCentre)
|
||||||
{
|
{
|
||||||
toggleAxisAvailable(xCheckBox.Current, true);
|
toggleAxisAvailable(xCheckBox.Current, true);
|
||||||
toggleAxisAvailable(yCheckBox.Current, true);
|
toggleAxisAvailable(yCheckBox.Current, true);
|
||||||
@ -208,7 +263,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float min_scale = 0.5f;
|
const float min_scale = 0.05f;
|
||||||
const float max_scale = 10;
|
const float max_scale = 10;
|
||||||
|
|
||||||
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
||||||
@ -230,7 +285,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y));
|
scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOrigin(ScaleOrigin origin)
|
private void setOrigin(EditorOrigin origin)
|
||||||
{
|
{
|
||||||
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
||||||
updateMinMaxScale();
|
updateMinMaxScale();
|
||||||
@ -241,13 +296,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
switch (scale.Origin)
|
switch (scale.Origin)
|
||||||
{
|
{
|
||||||
case ScaleOrigin.GridCentre:
|
case EditorOrigin.GridCentre:
|
||||||
return gridToolbox.StartPosition.Value;
|
return gridToolbox.StartPosition.Value;
|
||||||
|
|
||||||
case ScaleOrigin.PlayfieldCentre:
|
case EditorOrigin.PlayfieldCentre:
|
||||||
return OsuPlayfield.BASE_SIZE / 2;
|
return OsuPlayfield.BASE_SIZE / 2;
|
||||||
|
|
||||||
case ScaleOrigin.SelectionCentre:
|
case EditorOrigin.SelectionCentre:
|
||||||
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
|
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
|
||||||
return slider.Position;
|
return slider.Position;
|
||||||
|
|
||||||
@ -271,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
|
private float getRotation(PreciseScaleInfo scale) => scale.Origin == EditorOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
@ -299,12 +354,5 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ScaleOrigin
|
public record PreciseScaleInfo(float Scale, EditorOrigin Origin, bool XAxis, bool YAxis);
|
||||||
{
|
|
||||||
GridCentre,
|
|
||||||
PlayfieldCentre,
|
|
||||||
SelectionCentre
|
|
||||||
}
|
|
||||||
|
|
||||||
public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis);
|
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
AddStep("load player", () =>
|
AddStep("load player", () =>
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
Ruleset.Value = new TaikoRuleset().RulesetInfo;
|
||||||
SelectedMods.Value = mods ?? Array.Empty<Mod>();
|
SelectedMods.Value = mods ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
@ -1,33 +1,55 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the stamina coefficient of taiko difficulty.
|
/// Calculates the stamina coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Stamina : StrainDecaySkill
|
public class Stamina : StrainSkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1.1;
|
private double skillMultiplier => 1.1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
private double strainDecayBase => 0.4;
|
||||||
|
|
||||||
|
private readonly bool singleColourStamina;
|
||||||
|
|
||||||
|
private double currentStrain;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="Stamina"/> skill.
|
/// Creates a <see cref="Stamina"/> skill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mods">Mods for use in skill calculations.</param>
|
/// <param name="mods">Mods for use in skill calculations.</param>
|
||||||
public Stamina(Mod[] mods)
|
/// <param name="singleColourStamina">Reads when Stamina is from a single coloured pattern.</param>
|
||||||
|
public Stamina(Mod[] mods, bool singleColourStamina)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
|
this.singleColourStamina = singleColourStamina;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
return StaminaEvaluator.EvaluateDifficultyOf(current);
|
currentStrain *= strainDecay(current.DeltaTime);
|
||||||
|
currentStrain += StaminaEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
|
||||||
|
|
||||||
|
// Safely prevents previous strains from shifting as new notes are added.
|
||||||
|
var currentObject = current as TaikoDifficultyHitObject;
|
||||||
|
int index = currentObject?.Colour.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;
|
||||||
|
|
||||||
|
if (singleColourStamina)
|
||||||
|
return currentStrain / (1 + Math.Exp(-(index - 10) / 2.0));
|
||||||
|
|
||||||
|
return currentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => singleColourStamina ? 0 : currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
[JsonProperty("stamina_difficulty")]
|
[JsonProperty("stamina_difficulty")]
|
||||||
public double StaminaDifficulty { get; set; }
|
public double StaminaDifficulty { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ratio of stamina difficulty from mono-color (single colour) streams to total stamina difficulty.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("mono_stamina_factor")]
|
||||||
|
public double MonoStaminaFactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The difficulty corresponding to the rhythm skill.
|
/// The difficulty corresponding to the rhythm skill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -60,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
|
yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
|
||||||
|
yield return (ATTRIB_ID_MONO_STAMINA_FACTOR, MonoStaminaFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
@ -69,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||||
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
|
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
|
||||||
|
MonoStaminaFactor = values[ATTRIB_ID_MONO_STAMINA_FACTOR];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
new Rhythm(mods),
|
new Rhythm(mods),
|
||||||
new Colour(mods),
|
new Colour(mods),
|
||||||
new Stamina(mods)
|
new Stamina(mods, false),
|
||||||
|
new Stamina(mods, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,14 +80,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
Colour colour = (Colour)skills.First(x => x is Colour);
|
Colour colour = (Colour)skills.First(x => x is Colour);
|
||||||
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
||||||
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
|
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
|
||||||
|
Stamina singleColourStamina = (Stamina)skills.Last(x => x is Stamina);
|
||||||
|
|
||||||
double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
|
double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
|
||||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||||
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
|
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
|
||||||
|
double monoStaminaRating = singleColourStamina.DifficultyValue() * stamina_skill_multiplier;
|
||||||
|
double monoStaminaFactor = staminaRating == 0 ? 1 : Math.Pow(monoStaminaRating / staminaRating, 5);
|
||||||
|
|
||||||
double combinedRating = combinedDifficultyValue(rhythm, colour, stamina);
|
double combinedRating = combinedDifficultyValue(rhythm, colour, stamina);
|
||||||
double starRating = rescale(combinedRating * 1.4);
|
double starRating = rescale(combinedRating * 1.4);
|
||||||
|
|
||||||
|
// TODO: This is temporary measure as we don't detect abuse of multiple-input playstyles of converts within the current system.
|
||||||
|
if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0)
|
||||||
|
{
|
||||||
|
starRating *= 0.925;
|
||||||
|
// For maps with low colour variance and high stamina requirement, multiple inputs are more likely to be abused.
|
||||||
|
if (colourRating < 2 && staminaRating > 8)
|
||||||
|
starRating *= 0.80;
|
||||||
|
}
|
||||||
|
|
||||||
HitWindows hitWindows = new TaikoHitWindows();
|
HitWindows hitWindows = new TaikoHitWindows();
|
||||||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||||
|
|
||||||
@ -95,6 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
StarRating = starRating,
|
StarRating = starRating,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
StaminaDifficulty = staminaRating,
|
StaminaDifficulty = staminaRating,
|
||||||
|
MonoStaminaFactor = monoStaminaFactor,
|
||||||
RhythmDifficulty = rhythmRating,
|
RhythmDifficulty = rhythmRating,
|
||||||
ColourDifficulty = colourRating,
|
ColourDifficulty = colourRating,
|
||||||
PeakDifficulty = combinedRating,
|
PeakDifficulty = combinedRating,
|
||||||
|
@ -42,18 +42,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (totalSuccessfulHits > 0)
|
if (totalSuccessfulHits > 0)
|
||||||
effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss;
|
effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss;
|
||||||
|
|
||||||
// TODO: The detection of rulesets is temporary until the leftover old skills have been reworked.
|
// Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calculation.
|
||||||
bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1;
|
bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1;
|
||||||
|
|
||||||
double multiplier = 1.13;
|
double multiplier = 1.13;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModHidden))
|
if (score.Mods.Any(m => m is ModHidden) && !isConvert)
|
||||||
multiplier *= 1.075;
|
multiplier *= 1.075;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModEasy))
|
if (score.Mods.Any(m => m is ModEasy))
|
||||||
multiplier *= 0.975;
|
multiplier *= 0.950;
|
||||||
|
|
||||||
double difficultyValue = computeDifficultyValue(score, taikoAttributes, isConvert);
|
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
|
||||||
double accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert);
|
double accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert);
|
||||||
double totalValue =
|
double totalValue =
|
||||||
Math.Pow(
|
Math.Pow(
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
|
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;
|
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;
|
||||||
|
|
||||||
@ -81,21 +81,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
difficultyValue *= Math.Pow(0.986, effectiveMissCount);
|
difficultyValue *= Math.Pow(0.986, effectiveMissCount);
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModEasy))
|
if (score.Mods.Any(m => m is ModEasy))
|
||||||
difficultyValue *= 0.985;
|
difficultyValue *= 0.90;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModHidden) && !isConvert)
|
if (score.Mods.Any(m => m is ModHidden))
|
||||||
difficultyValue *= 1.025;
|
difficultyValue *= 1.025;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModHardRock))
|
if (score.Mods.Any(m => m is ModHardRock))
|
||||||
difficultyValue *= 1.10;
|
difficultyValue *= 1.10;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
||||||
difficultyValue *= 1.050 * lengthBonus;
|
difficultyValue *= Math.Max(1, 1.050 - Math.Min(attributes.MonoStaminaFactor / 50, 1) * lengthBonus);
|
||||||
|
|
||||||
if (estimatedUnstableRate == null)
|
if (estimatedUnstableRate == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0);
|
// Scale accuracy more harshly on nearly-completely mono (single coloured) speed maps.
|
||||||
|
double accScalingExponent = 2 + attributes.MonoStaminaFactor;
|
||||||
|
double accScalingShift = 300 - 100 * attributes.MonoStaminaFactor;
|
||||||
|
|
||||||
|
return difficultyValue * Math.Pow(SpecialFunctions.Erf(accScalingShift / (Math.Sqrt(2) * estimatedUnstableRate.Value)), accScalingExponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
|
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
|
||||||
|
@ -7,6 +7,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Playfield CreatePlayfield() => new TaikoEditorPlayfield();
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
25
osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs
Normal file
25
osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public partial class TaikoEditorPlayfield : TaikoPlayfield
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// This is the simplest way to extend the taiko playfield beyond the left of the drum area.
|
||||||
|
// Required in the editor to not look weird underneath left toolbox area.
|
||||||
|
AddInternal(new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
public override Quad ScreenSpaceDrawQuad => MainPiece.Drawable.ScreenSpaceDrawQuad;
|
public override Quad ScreenSpaceDrawQuad => MainPiece.Drawable.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
|
// done strictly for editor purposes.
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => MainPiece.Drawable.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rolling number of tick hits. This increases for hits and decreases for misses.
|
/// Rolling number of tick hits. This increases for hits and decreases for misses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||||
@ -19,13 +21,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var headDrawQuad = headCircle.ScreenSpaceDrawQuad;
|
// the reason why this calculation is so involved is that the head & tail sprites have different sizes/radii.
|
||||||
var tailDrawQuad = tailCircle.ScreenSpaceDrawQuad;
|
// therefore naively taking the SSDQs of them and making a quad out of them results in a trapezoid shape and not a box.
|
||||||
|
var headCentre = headCircle.ScreenSpaceDrawQuad.Centre;
|
||||||
|
var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2;
|
||||||
|
|
||||||
return new Quad(headDrawQuad.TopLeft, tailDrawQuad.TopRight, headDrawQuad.BottomLeft, tailDrawQuad.BottomRight);
|
float headRadius = headCircle.ScreenSpaceDrawQuad.Height / 2;
|
||||||
|
float tailRadius = tailCircle.ScreenSpaceDrawQuad.Height / 2;
|
||||||
|
float radius = Math.Max(headRadius, tailRadius);
|
||||||
|
|
||||||
|
var rectangle = new RectangleF(headCentre.X, headCentre.Y, tailCentre.X - headCentre.X, 0).Inflate(radius);
|
||||||
|
return new Quad(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomLeft, rectangle.BottomRight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos);
|
||||||
|
|
||||||
private LegacyCirclePiece headCircle = null!;
|
private LegacyCirclePiece headCircle = null!;
|
||||||
|
|
||||||
private Sprite body = null!;
|
private Sprite body = null!;
|
||||||
|
@ -241,8 +241,8 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
||||||
|
|
||||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -273,34 +273,6 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch)
|
|
||||||
{
|
|
||||||
var lookupResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapID = 654321,
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"cafebabe",
|
|
||||||
};
|
|
||||||
|
|
||||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
|
||||||
targetMock.Setup(src => src.Available).Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.IsAny<BeatmapInfo>(), out lookupResult))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var beatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
MD5Hash = @"deadbeef"
|
|
||||||
};
|
|
||||||
var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
|
|
||||||
beatmap.BeatmapSet = beatmapSet;
|
|
||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
|
||||||
|
|
||||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
|
public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
|
||||||
{
|
{
|
||||||
@ -383,58 +355,5 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch)
|
|
||||||
{
|
|
||||||
var firstResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapID = 654321,
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"cafebabe"
|
|
||||||
};
|
|
||||||
var secondResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"dededede"
|
|
||||||
};
|
|
||||||
|
|
||||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
|
||||||
targetMock.Setup(src => src.Available).Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 654321), out firstResult))
|
|
||||||
.Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 666666), out secondResult))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var firstBeatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
OnlineID = 654321,
|
|
||||||
MD5Hash = @"cafebabe",
|
|
||||||
};
|
|
||||||
var secondBeatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
OnlineID = 666666,
|
|
||||||
MD5Hash = @"deadbeef"
|
|
||||||
};
|
|
||||||
var beatmapSet = new BeatmapSetInfo(new[]
|
|
||||||
{
|
|
||||||
firstBeatmap,
|
|
||||||
secondBeatmap
|
|
||||||
});
|
|
||||||
firstBeatmap.BeatmapSet = beatmapSet;
|
|
||||||
secondBeatmap.BeatmapSet = beatmapSet;
|
|
||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
|
||||||
|
|
||||||
Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
|
||||||
Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321));
|
|
||||||
|
|
||||||
Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1));
|
|
||||||
|
|
||||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
|
private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
|
||||||
{
|
{
|
||||||
// Check all control points that are still considered to be at a global level.
|
// Check all control points that are still considered to be at a global level.
|
||||||
Assert.That(expected.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.TimingPoints.Serialize()));
|
Assert.That(actual.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.TimingPoints.Serialize()));
|
||||||
Assert.That(expected.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.EffectPoints.Serialize()));
|
Assert.That(actual.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.EffectPoints.Serialize()));
|
||||||
|
|
||||||
// Check all hitobjects.
|
// Check all hitobjects.
|
||||||
Assert.That(expected.beatmap.HitObjects.Serialize(), Is.EqualTo(actual.beatmap.HitObjects.Serialize()));
|
Assert.That(actual.beatmap.HitObjects.Serialize(), Is.EqualTo(expected.beatmap.HitObjects.Serialize()));
|
||||||
|
|
||||||
// Check skin.
|
// Check skin.
|
||||||
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
||||||
|
39
osu.Game.Tests/Resources/mania-0-01-sv.osu
Normal file
39
osu.Game.Tests/Resources/mania-0-01-sv.osu
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
SampleSet: Normal
|
||||||
|
StackLeniency: 0.7
|
||||||
|
Mode: 3
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:3
|
||||||
|
CircleSize:5
|
||||||
|
OverallDifficulty:8
|
||||||
|
ApproachRate:8
|
||||||
|
SliderMultiplier:3.59999990463257
|
||||||
|
SliderTickRate:2
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
24,352.941176470588,4,1,1,100,1,0
|
||||||
|
6376,-10000,4,1,1,100,0,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
51,192,24,1,0,0:0:0:0:
|
||||||
|
153,192,200,1,0,0:0:0:0:
|
||||||
|
358,192,376,1,0,0:0:0:0:
|
||||||
|
460,192,553,1,0,0:0:0:0:
|
||||||
|
460,192,729,128,0,1435:0:0:0:0:
|
||||||
|
358,192,906,128,0,1612:0:0:0:0:
|
||||||
|
256,192,1082,128,0,1788:0:0:0:0:
|
||||||
|
153,192,1259,128,0,1965:0:0:0:0:
|
||||||
|
51,192,1435,128,0,2141:0:0:0:0:
|
||||||
|
51,192,2318,1,12,0:0:0:0:
|
||||||
|
153,192,2318,1,4,0:0:0:0:
|
||||||
|
256,192,2318,1,6,0:0:0:0:
|
||||||
|
358,192,2318,1,14,0:0:0:0:
|
||||||
|
460,192,2318,1,0,0:0:0:0:
|
||||||
|
51,192,2494,128,0,2582:0:0:0:0:
|
||||||
|
153,192,2494,128,14,2582:0:0:0:0:
|
||||||
|
256,192,2494,128,6,2582:0:0:0:0:
|
||||||
|
358,192,2494,128,4,2582:0:0:0:0:
|
||||||
|
460,192,2494,128,12,2582:0:0:0:0:
|
@ -155,7 +155,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var api = (DummyAPIAccess)API;
|
var api = (DummyAPIAccess)API;
|
||||||
|
|
||||||
api.Friends.Clear();
|
api.Friends.Clear();
|
||||||
api.Friends.Add(friend);
|
api.Friends.Add(new APIRelation
|
||||||
|
{
|
||||||
|
Mutual = true,
|
||||||
|
RelationType = RelationType.Friend,
|
||||||
|
TargetID = friend.OnlineID,
|
||||||
|
TargetUser = friend
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
int playerNumber = 1;
|
int playerNumber = 1;
|
||||||
|
@ -30,14 +30,20 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
if (supportLevel > 3)
|
if (supportLevel > 3)
|
||||||
supportLevel = 0;
|
supportLevel = 0;
|
||||||
|
|
||||||
((DummyAPIAccess)API).Friends.Add(new APIUser
|
((DummyAPIAccess)API).Friends.Add(new APIRelation
|
||||||
{
|
{
|
||||||
Username = @"peppy",
|
TargetID = 2,
|
||||||
Id = 2,
|
RelationType = RelationType.Friend,
|
||||||
Colour = "99EB47",
|
Mutual = true,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
TargetUser = new APIUser
|
||||||
IsSupporter = supportLevel > 0,
|
{
|
||||||
SupportLevel = supportLevel
|
Username = @"peppy",
|
||||||
|
Id = 2,
|
||||||
|
Colour = "99EB47",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
IsSupporter = supportLevel > 0,
|
||||||
|
SupportLevel = supportLevel
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,20 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Profile;
|
using osu.Game.Overlays.Profile;
|
||||||
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -22,6 +27,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
|
private readonly ManualResetEventSlim requestLock = new ManualResetEventSlim();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager configManager { get; set; } = null!;
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
@ -400,5 +409,97 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
}, new OsuRuleset().RulesetInfo));
|
}, new OsuRuleset().RulesetInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private APIUser nonFriend => new APIUser
|
||||||
|
{
|
||||||
|
Id = 727,
|
||||||
|
Username = "Whatever",
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddFriend()
|
||||||
|
{
|
||||||
|
AddStep("Setup request", () =>
|
||||||
|
{
|
||||||
|
requestLock.Reset();
|
||||||
|
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (request is not AddFriendRequest req)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (req.TargetId != nonFriend.OnlineID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var apiRelation = new APIRelation
|
||||||
|
{
|
||||||
|
TargetID = nonFriend.OnlineID,
|
||||||
|
Mutual = true,
|
||||||
|
RelationType = RelationType.Friend,
|
||||||
|
TargetUser = nonFriend
|
||||||
|
};
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
requestLock.Wait(3000);
|
||||||
|
dummyAPI.Friends.Add(apiRelation);
|
||||||
|
req.TriggerSuccess(new AddFriendResponse
|
||||||
|
{
|
||||||
|
UserRelation = apiRelation
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
|
||||||
|
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
|
||||||
|
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
|
||||||
|
AddStep("Complete request", () => requestLock.Set());
|
||||||
|
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddFriendNonMutual()
|
||||||
|
{
|
||||||
|
AddStep("Setup request", () =>
|
||||||
|
{
|
||||||
|
requestLock.Reset();
|
||||||
|
|
||||||
|
dummyAPI.HandleRequest = request =>
|
||||||
|
{
|
||||||
|
if (request is not AddFriendRequest req)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (req.TargetId != nonFriend.OnlineID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var apiRelation = new APIRelation
|
||||||
|
{
|
||||||
|
TargetID = nonFriend.OnlineID,
|
||||||
|
Mutual = false,
|
||||||
|
RelationType = RelationType.Friend,
|
||||||
|
TargetUser = nonFriend
|
||||||
|
};
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
requestLock.Wait(3000);
|
||||||
|
dummyAPI.Friends.Add(apiRelation);
|
||||||
|
req.TriggerSuccess(new AddFriendResponse
|
||||||
|
{
|
||||||
|
UserRelation = apiRelation
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
|
||||||
|
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
|
||||||
|
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
|
||||||
|
AddStep("Complete request", () => requestLock.Set());
|
||||||
|
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,11 +414,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
});
|
});
|
||||||
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre));
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre));
|
||||||
scrollToAndStartBinding("Left (centre)");
|
scrollToAndStartBinding("Left (centre)");
|
||||||
AddStep("clear binding", () =>
|
clearBinding();
|
||||||
{
|
|
||||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
|
||||||
row.ChildrenOfType<KeyBindingRow.ClearButton>().Single().TriggerClick();
|
|
||||||
});
|
|
||||||
scrollToAndStartBinding("Left (rim)");
|
scrollToAndStartBinding("Left (rim)");
|
||||||
AddStep("bind M1", () => InputManager.Click(MouseButton.Left));
|
AddStep("bind M1", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -431,6 +427,45 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
AddUntilStep("conflict popover not shown", () => panel.ChildrenOfType<KeyBindingConflictPopover>().SingleOrDefault(), () => Is.Null);
|
AddUntilStep("conflict popover not shown", () => panel.ChildrenOfType<KeyBindingConflictPopover>().SingleOrDefault(), () => Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResettingRowCannotConflictWithItself()
|
||||||
|
{
|
||||||
|
AddStep("reset taiko section to default", () =>
|
||||||
|
{
|
||||||
|
var section = panel.ChildrenOfType<VariantBindingsSubsection>().First(section => new TaikoRuleset().RulesetInfo.Equals(section.Ruleset));
|
||||||
|
section.ChildrenOfType<ResetButton>().Single().TriggerClick();
|
||||||
|
});
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
scrollToAndStartBinding("Left (centre)");
|
||||||
|
clearBinding();
|
||||||
|
scrollToAndStartBinding("Left (centre)", 1);
|
||||||
|
clearBinding();
|
||||||
|
|
||||||
|
scrollToAndStartBinding("Left (centre)");
|
||||||
|
AddStep("bind F", () => InputManager.Key(Key.F));
|
||||||
|
scrollToAndStartBinding("Left (centre)", 1);
|
||||||
|
AddStep("bind M1", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("revert row to default", () =>
|
||||||
|
{
|
||||||
|
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||||
|
InputManager.MoveMouseTo(row.ChildrenOfType<RevertToDefaultButton<bool>>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddWaitStep("wait a bit", 3);
|
||||||
|
AddUntilStep("conflict popover not shown", () => panel.ChildrenOfType<KeyBindingConflictPopover>().SingleOrDefault(), () => Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearBinding()
|
||||||
|
{
|
||||||
|
AddStep("clear binding", () =>
|
||||||
|
{
|
||||||
|
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||||
|
row.ChildrenOfType<KeyBindingRow.ClearButton>().Single().TriggerClick();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void checkBinding(string name, string keyName)
|
private void checkBinding(string name, string keyName)
|
||||||
{
|
{
|
||||||
AddAssert($"Check {name} is bound to {keyName}", () =>
|
AddAssert($"Check {name} is bound to {keyName}", () =>
|
||||||
@ -442,23 +477,23 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
}, () => Is.EqualTo(keyName));
|
}, () => Is.EqualTo(keyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToAndStartBinding(string name)
|
private void scrollToAndStartBinding(string name, int bindingIndex = 0)
|
||||||
{
|
{
|
||||||
KeyBindingRow.KeyButton firstButton = null;
|
KeyBindingRow.KeyButton targetButton = null;
|
||||||
|
|
||||||
AddStep($"Scroll to {name}", () =>
|
AddStep($"Scroll to {name}", () =>
|
||||||
{
|
{
|
||||||
var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == name));
|
var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == name));
|
||||||
firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First();
|
targetButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(bindingIndex);
|
||||||
|
|
||||||
panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollTo(firstButton);
|
panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollTo(targetButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddWaitStep("wait for scroll", 5);
|
AddWaitStep("wait for scroll", 5);
|
||||||
|
|
||||||
AddStep("click to bind", () =>
|
AddStep("click to bind", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(firstButton);
|
InputManager.MoveMouseTo(targetButton);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning.Components;
|
using osu.Game.Skinning.Components;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
@ -30,6 +44,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
SelectedMods.SetDefault();
|
||||||
|
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
{
|
{
|
||||||
BeatmapInfo =
|
BeatmapInfo =
|
||||||
@ -93,6 +109,126 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("check new title", getText, () => Is.EqualTo("Title: Another"));
|
AddAssert("check new title", getText, () => Is.EqualTo("Title: Another"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithMods()
|
||||||
|
{
|
||||||
|
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BPM = 100,
|
||||||
|
Length = 30000,
|
||||||
|
Difficulty =
|
||||||
|
{
|
||||||
|
ApproachRate = 10,
|
||||||
|
CircleSize = 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00");
|
||||||
|
test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20");
|
||||||
|
test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00");
|
||||||
|
test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00");
|
||||||
|
|
||||||
|
void test(BeatmapAttribute attribute, Mod mod, string before, string after)
|
||||||
|
{
|
||||||
|
AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute);
|
||||||
|
AddAssert("check text is correct", getText, () => Is.EqualTo(before));
|
||||||
|
|
||||||
|
AddStep("add DT mod", () => SelectedMods.Value = new[] { mod });
|
||||||
|
AddAssert("check text is correct", getText, () => Is.EqualTo(after));
|
||||||
|
AddStep("clear mods", () => SelectedMods.SetDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStarRating()
|
||||||
|
{
|
||||||
|
AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo);
|
||||||
|
AddStep("set star rating attribute", () => text.Attribute.Value = BeatmapAttribute.StarRating);
|
||||||
|
AddAssert("check star rating is 0", getText, () => Is.EqualTo("Star Rating: 0.00"));
|
||||||
|
|
||||||
|
// Adding mod
|
||||||
|
TestMod mod = null!;
|
||||||
|
AddStep("add mod with difficulty 1", () => SelectedMods.Value = new[] { mod = new TestMod { Difficulty = { Value = 1 } } });
|
||||||
|
AddUntilStep("check star rating is 1", getText, () => Is.EqualTo("Star Rating: 1.00"));
|
||||||
|
|
||||||
|
// Changing mod setting
|
||||||
|
AddStep("change mod difficulty to 2", () => mod.Difficulty.Value = 2);
|
||||||
|
AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00"));
|
||||||
|
}
|
||||||
|
|
||||||
private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();
|
private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();
|
||||||
|
|
||||||
|
private class TestRuleset : Ruleset
|
||||||
|
{
|
||||||
|
public override IEnumerable<Mod> GetModsFor(ModType type) => new[]
|
||||||
|
{
|
||||||
|
new TestMod()
|
||||||
|
};
|
||||||
|
|
||||||
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap)
|
||||||
|
=> new OsuRuleset().CreateBeatmapConverter(beatmap);
|
||||||
|
|
||||||
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap)
|
||||||
|
=> new TestDifficultyCalculator(new TestRuleset().RulesetInfo, beatmap);
|
||||||
|
|
||||||
|
public override PerformanceCalculator CreatePerformanceCalculator()
|
||||||
|
=> new TestPerformanceCalculator(new TestRuleset());
|
||||||
|
|
||||||
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||||
|
=> null!;
|
||||||
|
|
||||||
|
public override string Description => string.Empty;
|
||||||
|
public override string ShortName => string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDifficultyCalculator : DifficultyCalculator
|
||||||
|
{
|
||||||
|
public TestDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
|
: base(ruleset, beatmap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||||
|
=> new DifficultyAttributes(mods, mods.OfType<TestMod>().SingleOrDefault()?.Difficulty.Value ?? 0);
|
||||||
|
|
||||||
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
|
=> Array.Empty<DifficultyHitObject>();
|
||||||
|
|
||||||
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||||
|
=> Array.Empty<Skill>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPerformanceCalculator : PerformanceCalculator
|
||||||
|
{
|
||||||
|
public TestPerformanceCalculator(Ruleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||||
|
=> new PerformanceAttributes { Total = score.Mods.OfType<TestMod>().SingleOrDefault()?.Performance.Value ?? 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMod : Mod
|
||||||
|
{
|
||||||
|
[SettingSource("difficulty")]
|
||||||
|
public BindableDouble Difficulty { get; } = new BindableDouble(0);
|
||||||
|
|
||||||
|
[SettingSource("performance")]
|
||||||
|
public BindableDouble Performance { get; } = new BindableDouble(0);
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public TestMod()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => string.Empty;
|
||||||
|
public override LocalisableString Description => string.Empty;
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override string Acronym => "Test";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||||
|
|
||||||
var req = new GetBeatmapRequest(beatmapInfo);
|
var req = new GetBeatmapRequest(md5Hash: beatmapInfo.MD5Hash, filename: beatmapInfo.Path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
@ -44,10 +43,19 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
|
// note that these lookups DO NOT ACTUALLY FULLY GUARANTEE that the beatmap is what it claims it is,
|
||||||
|
// i.e. the correctness of this lookup should be treated as APPROXIMATE AT WORST.
|
||||||
|
// this is because the beatmap filename is used as a fallback in some scenarios where the MD5 of the beatmap may mismatch.
|
||||||
|
// this is considered to be an acceptable casualty so that things can continue to work as expected for users in some rare scenarios
|
||||||
|
// (stale beatmap files in beatmap packs, beatmap mirror desyncs).
|
||||||
|
// however, all this means that other places such as score submission ARE EXPECTED TO VERIFY THE MD5 OF THE BEATMAP AGAINST THE ONLINE ONE EXPLICITLY AGAIN.
|
||||||
|
//
|
||||||
|
// additionally note that the online ID stored to the map is EXPLICITLY NOT USED because some users in a silly attempt to "fix" things for themselves on stable
|
||||||
|
// would reuse online IDs of already submitted beatmaps, which means that information is pretty much expected to be bogus in a nonzero number of beatmapsets.
|
||||||
if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res))
|
if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (res == null || shouldDiscardLookupResult(res, beatmapInfo))
|
if (res == null)
|
||||||
{
|
{
|
||||||
beatmapInfo.ResetOnlineInfo();
|
beatmapInfo.ResetOnlineInfo();
|
||||||
lookupResults.Add(null); // mark lookup failure
|
lookupResults.Add(null); // mark lookup failure
|
||||||
@ -83,23 +91,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo)
|
|
||||||
{
|
|
||||||
if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID)
|
|
||||||
{
|
|
||||||
Logger.Log($"Discarding metadata lookup result due to mismatching online ID (expected: {beatmapInfo.OnlineID} actual: {result.BeatmapID})", LoggingTarget.Database);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash)
|
|
||||||
{
|
|
||||||
Logger.Log($"Discarding metadata lookup result due to mismatching hash (expected: {beatmapInfo.MD5Hash} actual: {result.MD5Hash})", LoggingTarget.Database);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to retrieve the <see cref="OnlineBeatmapMetadata"/> for the given <paramref name="beatmapInfo"/>.
|
/// Attempts to retrieve the <see cref="OnlineBeatmapMetadata"/> for the given <paramref name="beatmapInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -183,7 +183,17 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (scrollSpeedEncodedAsSliderVelocity)
|
if (scrollSpeedEncodedAsSliderVelocity)
|
||||||
{
|
{
|
||||||
foreach (var point in legacyControlPoints.EffectPoints)
|
foreach (var point in legacyControlPoints.EffectPoints)
|
||||||
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
{
|
||||||
|
legacyControlPoints.Add(point.Time, new DifficultyControlPoint
|
||||||
|
{
|
||||||
|
SliderVelocityBindable =
|
||||||
|
{
|
||||||
|
MinValue = point.ScrollSpeedBindable.MinValue,
|
||||||
|
MaxValue = point.ScrollSpeedBindable.MaxValue,
|
||||||
|
Value = point.ScrollSpeedBindable.Value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties();
|
LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties();
|
||||||
|
@ -90,8 +90,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
||||||
&& string.IsNullOrEmpty(beatmapInfo.Path)
|
&& string.IsNullOrEmpty(beatmapInfo.Path))
|
||||||
&& beatmapInfo.OnlineID <= 0)
|
|
||||||
{
|
{
|
||||||
onlineMetadata = null;
|
onlineMetadata = null;
|
||||||
return false;
|
return false;
|
||||||
@ -240,10 +239,9 @@ namespace osu.Game.Beatmaps
|
|||||||
using var cmd = db.CreateCommand();
|
using var cmd = db.CreateCommand();
|
||||||
|
|
||||||
cmd.CommandText =
|
cmd.CommandText =
|
||||||
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";
|
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR filename = @Path";
|
||||||
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
@ -281,11 +279,10 @@ namespace osu.Game.Beatmaps
|
|||||||
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
|
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
|
||||||
FROM `osu_beatmaps` AS `b`
|
FROM `osu_beatmaps` AS `b`
|
||||||
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
|
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
|
||||||
WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path
|
WHERE `b`.`checksum` = @MD5Hash OR `b`.`filename` = @Path
|
||||||
""";
|
""";
|
||||||
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Localisation;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods.Input;
|
using osu.Game.Overlays.Mods.Input;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
@ -193,6 +194,8 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
|
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
|
||||||
SetDefault(OsuSetting.EditorLimitedDistanceSnap, false);
|
SetDefault(OsuSetting.EditorLimitedDistanceSnap, false);
|
||||||
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
|
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
|
||||||
|
SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre);
|
||||||
|
SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre);
|
||||||
|
|
||||||
SetDefault(OsuSetting.HideCountryFlags, false);
|
SetDefault(OsuSetting.HideCountryFlags, false);
|
||||||
|
|
||||||
@ -204,8 +207,11 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
|
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
|
||||||
|
|
||||||
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
|
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
|
||||||
|
SetDefault(OsuSetting.EditorTimelineShowBreaks, true);
|
||||||
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
|
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
|
||||||
|
|
||||||
|
SetDefault(OsuSetting.EditorContractSidebars, false);
|
||||||
|
|
||||||
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +437,10 @@ namespace osu.Game.Configuration
|
|||||||
HideCountryFlags,
|
HideCountryFlags,
|
||||||
EditorTimelineShowTimingChanges,
|
EditorTimelineShowTimingChanges,
|
||||||
EditorTimelineShowTicks,
|
EditorTimelineShowTicks,
|
||||||
AlwaysShowHoldForMenuButton
|
AlwaysShowHoldForMenuButton,
|
||||||
|
EditorContractSidebars,
|
||||||
|
EditorScaleOrigin,
|
||||||
|
EditorRotationOrigin,
|
||||||
|
EditorTimelineShowBreaks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -528,7 +528,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="model">The new model proposed for import.</param>
|
/// <param name="model">The new model proposed for import.</param>
|
||||||
/// <param name="realm">The current realm context.</param>
|
/// <param name="realm">The current realm context.</param>
|
||||||
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
|
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
|
||||||
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().FirstOrDefault(b => b.Hash == model.Hash);
|
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether import can be skipped after finding an existing import early in the process.
|
/// Whether import can be skipped after finding an existing import early in the process.
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
if (Link != null)
|
if (Link != null)
|
||||||
{
|
{
|
||||||
items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => game?.OpenUrlExternally(Link)));
|
items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => game?.OpenUrlExternally(Link)));
|
||||||
items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, copyUrl));
|
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, copyUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
const float vertical_offset = 13;
|
const float vertical_offset = 13;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
label = new OsuSpriteText
|
label = new OsuSpriteText
|
||||||
{
|
{
|
||||||
@ -115,7 +115,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
KeyboardStep = 0.1f,
|
KeyboardStep = 0.1f,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Y = vertical_offset,
|
Y = vertical_offset,
|
||||||
}
|
},
|
||||||
|
upperBound.Nub.CreateProxy(),
|
||||||
|
lowerBound.Nub.CreateProxy(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +162,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected partial class BoundSlider : RoundedSliderBar<double>
|
protected partial class BoundSlider : RoundedSliderBar<double>
|
||||||
{
|
{
|
||||||
|
public new Nub Nub => base.Nub;
|
||||||
|
|
||||||
public string? DefaultString;
|
public string? DefaultString;
|
||||||
public LocalisableString? DefaultTooltip;
|
public LocalisableString? DefaultTooltip;
|
||||||
public string? TooltipSuffix;
|
public string? TooltipSuffix;
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -25,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private readonly HoverClickSounds hoverClickSounds;
|
private readonly HoverClickSounds hoverClickSounds;
|
||||||
|
|
||||||
|
private readonly Container mainContent;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -62,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Padding = new MarginPadding { Horizontal = 2 },
|
Padding = new MarginPadding { Horizontal = 2 },
|
||||||
Child = new CircularContainer
|
Child = mainContent = new CircularContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -135,6 +138,26 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = AccentColour.Darken(1),
|
||||||
|
Hollow = true,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = default;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateGlow();
|
updateGlow();
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -26,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private readonly HoverClickSounds hoverClickSounds;
|
private readonly HoverClickSounds hoverClickSounds;
|
||||||
|
|
||||||
|
private readonly Container mainContent;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -60,12 +63,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
RangePadding = EXPANDED_SIZE / 2;
|
RangePadding = EXPANDED_SIZE / 2;
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
mainContent = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Padding = new MarginPadding { Horizontal = 2 },
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -138,6 +142,26 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = AccentColour.Darken(1),
|
||||||
|
Hollow = true,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = default;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateGlow();
|
updateGlow();
|
||||||
@ -167,8 +191,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
protected override void UpdateValue(float value)
|
||||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
private Box flashLayer = null!;
|
private Box flashLayer = null!;
|
||||||
private FormTextBox.InnerTextBox textBox = null!;
|
private FormTextBox.InnerTextBox textBox = null!;
|
||||||
private Slider slider = null!;
|
private InnerSlider slider = null!;
|
||||||
private FormFieldCaption caption = null!;
|
private FormFieldCaption caption = null!;
|
||||||
private IFocusManager focusManager = null!;
|
private IFocusManager focusManager = null!;
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
},
|
},
|
||||||
TabbableContentContainer = tabbableContentContainer,
|
TabbableContentContainer = tabbableContentContainer,
|
||||||
},
|
},
|
||||||
slider = new Slider
|
slider = new InnerSlider
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
@ -163,6 +163,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
textBox.Current.BindValueChanged(textChanged);
|
textBox.Current.BindValueChanged(textChanged);
|
||||||
|
|
||||||
slider.IsDragging.BindValueChanged(_ => updateState());
|
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||||
|
slider.Focused.BindValueChanged(_ => updateState());
|
||||||
|
|
||||||
current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue;
|
current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue;
|
||||||
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
||||||
@ -259,16 +260,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
|
bool childHasFocus = slider.Focused.Value || textBox.Focused.Value;
|
||||||
|
|
||||||
textBox.Alpha = 1;
|
textBox.Alpha = 1;
|
||||||
|
|
||||||
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
||||||
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
|
||||||
BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0;
|
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||||
BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4;
|
BorderColour = childHasFocus ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
|
||||||
if (textBox.Focused.Value)
|
if (childHasFocus)
|
||||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||||
else if (IsHovered || slider.IsDragging.Value)
|
else if (IsHovered || slider.IsDragging.Value)
|
||||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||||
@ -283,8 +286,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString();
|
textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class Slider : OsuSliderBar<T>
|
private partial class InnerSlider : OsuSliderBar<T>
|
||||||
{
|
{
|
||||||
|
public BindableBool Focused { get; } = new BindableBool();
|
||||||
|
|
||||||
public BindableBool IsDragging { get; set; } = new BindableBool();
|
public BindableBool IsDragging { get; set; } = new BindableBool();
|
||||||
public Action? OnCommit { get; set; }
|
public Action? OnCommit { get; set; }
|
||||||
|
|
||||||
@ -344,7 +349,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,11 +386,25 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
Focused.Value = true;
|
||||||
|
base.OnFocus(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
Focused.Value = false;
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
rightBox.Colour = colourProvider.Background6;
|
rightBox.Colour = colourProvider.Background6;
|
||||||
leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2;
|
leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2;
|
||||||
nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
protected override void UpdateValue(float value)
|
||||||
|
@ -174,6 +174,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString General => new TranslatableString(getKey(@"general"), @"General");
|
public static LocalisableString General => new TranslatableString(getKey(@"general"), @"General");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Copy link"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CopyLink => new TranslatableString(getKey(@"copy_link"), @"Copy link");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -114,6 +114,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time");
|
public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Contract sidebars when not hovered"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ContractSidebars => new TranslatableString(getKey(@"contract_sidebars"), @"Contract sidebars when not hovered");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Must be in edit mode to handle editor links"
|
/// "Must be in edit mode to handle editor links"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -134,6 +139,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString TimelineShowTimingChanges => new TranslatableString(getKey(@"timeline_show_timing_changes"), @"Show timing changes");
|
public static LocalisableString TimelineShowTimingChanges => new TranslatableString(getKey(@"timeline_show_timing_changes"), @"Show timing changes");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Show breaks"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TimelineShowBreaks => new TranslatableString(getKey(@"timeline_show_breaks"), @"Show breaks");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Show ticks"
|
/// "Show ticks"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -26,10 +26,10 @@ namespace osu.Game.Localisation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "No performance points will be awarded.
|
/// "No performance points will be awarded.
|
||||||
/// Leaderboards may be reset by the beatmap creator."
|
/// Leaderboards may be reset."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString LovedBeatmapDisclaimerContent => new TranslatableString(getKey(@"loved_beatmap_disclaimer_content"), @"No performance points will be awarded.
|
public static LocalisableString LovedBeatmapDisclaimerContent => new TranslatableString(getKey(@"loved_beatmap_disclaimer_content"), @"No performance points will be awarded.
|
||||||
Leaderboards may be reset by the beatmap creator.");
|
Leaderboards may be reset.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "This beatmap is qualified"
|
/// "This beatmap is qualified"
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Online.API
|
|||||||
private string password;
|
private string password;
|
||||||
|
|
||||||
public IBindable<APIUser> LocalUser => localUser;
|
public IBindable<APIUser> LocalUser => localUser;
|
||||||
public IBindableList<APIUser> Friends => friends;
|
public IBindableList<APIRelation> Friends => friends;
|
||||||
public IBindable<UserActivity> Activity => activity;
|
public IBindable<UserActivity> Activity => activity;
|
||||||
public IBindable<UserStatistics> Statistics => statistics;
|
public IBindable<UserStatistics> Statistics => statistics;
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
|
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
|
||||||
|
|
||||||
private BindableList<APIUser> friends { get; } = new BindableList<APIUser>();
|
private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>();
|
||||||
|
|
||||||
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
|
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
@ -360,19 +360,7 @@ namespace osu.Game.Online.API
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var friendsReq = new GetFriendsRequest();
|
UpdateLocalFriends();
|
||||||
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
|
||||||
friendsReq.Success += res =>
|
|
||||||
{
|
|
||||||
friends.Clear();
|
|
||||||
friends.AddRange(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleRequest(friendsReq))
|
|
||||||
{
|
|
||||||
state.Value = APIState.Failing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||||
@ -624,6 +612,22 @@ namespace osu.Game.Online.API
|
|||||||
localUser.Value.Statistics = newStatistics;
|
localUser.Value.Statistics = newStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateLocalFriends()
|
||||||
|
{
|
||||||
|
if (!IsLoggedIn)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var friendsReq = new GetFriendsRequest();
|
||||||
|
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
||||||
|
friendsReq.Success += res =>
|
||||||
|
{
|
||||||
|
friends.Clear();
|
||||||
|
friends.AddRange(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Queue(friendsReq);
|
||||||
|
}
|
||||||
|
|
||||||
private static APIUser createGuestUser() => new GuestUser();
|
private static APIUser createGuestUser() => new GuestUser();
|
||||||
|
|
||||||
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Online.API
|
|||||||
Id = DUMMY_USER_ID,
|
Id = DUMMY_USER_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
public BindableList<APIUser> Friends { get; } = new BindableList<APIUser>();
|
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
|
||||||
|
|
||||||
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
@ -201,6 +201,10 @@ namespace osu.Game.Online.API
|
|||||||
LocalUser.Value.Statistics = newStatistics;
|
LocalUser.Value.Statistics = newStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateLocalFriends()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
||||||
|
|
||||||
public IChatClient GetChatClient() => new TestChatClientConnector(this);
|
public IChatClient GetChatClient() => new TestChatClientConnector(this);
|
||||||
@ -214,7 +218,7 @@ namespace osu.Game.Online.API
|
|||||||
public void SetState(APIState newState) => state.Value = newState;
|
public void SetState(APIState newState) => state.Value = newState;
|
||||||
|
|
||||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||||
IBindableList<APIUser> IAPIProvider.Friends => Friends;
|
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
|
||||||
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
||||||
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Online.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user's friends.
|
/// The user's friends.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindableList<APIUser> Friends { get; }
|
IBindableList<APIRelation> Friends { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current user's activity.
|
/// The current user's activity.
|
||||||
@ -134,6 +134,11 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void UpdateStatistics(UserStatistics newStatistics);
|
void UpdateStatistics(UserStatistics newStatistics);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the friends status of the current user.
|
||||||
|
/// </summary>
|
||||||
|
void UpdateLocalFriends();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedule a callback to run on the update thread.
|
/// Schedule a callback to run on the update thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
30
osu.Game/Online/API/Requests/AddFriendRequest.cs
Normal file
30
osu.Game/Online/API/Requests/AddFriendRequest.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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.Net.Http;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class AddFriendRequest : APIRequest<AddFriendResponse>
|
||||||
|
{
|
||||||
|
public readonly int TargetId;
|
||||||
|
|
||||||
|
public AddFriendRequest(int targetId)
|
||||||
|
{
|
||||||
|
TargetId = targetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
|
||||||
|
req.Method = HttpMethod.Post;
|
||||||
|
req.AddParameter("target", TargetId.ToString(), RequestParameterType.Query);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => @"friends";
|
||||||
|
}
|
||||||
|
}
|
14
osu.Game/Online/API/Requests/AddFriendResponse.cs
Normal file
14
osu.Game/Online/API/Requests/AddFriendResponse.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class AddFriendResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("user_relation")]
|
||||||
|
public APIRelation UserRelation = null!;
|
||||||
|
}
|
||||||
|
}
|
27
osu.Game/Online/API/Requests/DeleteFriendRequest.cs
Normal file
27
osu.Game/Online/API/Requests/DeleteFriendRequest.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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.Net.Http;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class DeleteFriendRequest : APIRequest
|
||||||
|
{
|
||||||
|
public readonly int TargetId;
|
||||||
|
|
||||||
|
public DeleteFriendRequest(int targetId)
|
||||||
|
{
|
||||||
|
TargetId = targetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
req.Method = HttpMethod.Delete;
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => $@"friends/{TargetId}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -9,23 +10,30 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
||||||
{
|
{
|
||||||
public readonly IBeatmapInfo BeatmapInfo;
|
public readonly int OnlineID;
|
||||||
public readonly string Filename;
|
public readonly string? MD5Hash;
|
||||||
|
public readonly string? Filename;
|
||||||
|
|
||||||
public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
|
public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
|
||||||
|
: this(onlineId: beatmapInfo.OnlineID, md5Hash: beatmapInfo.MD5Hash, filename: (beatmapInfo as BeatmapInfo)?.Path)
|
||||||
{
|
{
|
||||||
BeatmapInfo = beatmapInfo;
|
}
|
||||||
Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty;
|
|
||||||
|
public GetBeatmapRequest(int onlineId = -1, string? md5Hash = null, string? filename = null)
|
||||||
|
{
|
||||||
|
OnlineID = onlineId;
|
||||||
|
MD5Hash = md5Hash;
|
||||||
|
Filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
{
|
{
|
||||||
var request = base.CreateWebRequest();
|
var request = base.CreateWebRequest();
|
||||||
|
|
||||||
if (BeatmapInfo.OnlineID > 0)
|
if (OnlineID > 0)
|
||||||
request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString());
|
request.AddParameter(@"id", OnlineID.ToString(CultureInfo.InvariantCulture));
|
||||||
if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash))
|
if (!string.IsNullOrEmpty(MD5Hash))
|
||||||
request.AddParameter(@"checksum", BeatmapInfo.MD5Hash);
|
request.AddParameter(@"checksum", MD5Hash);
|
||||||
if (!string.IsNullOrEmpty(Filename))
|
if (!string.IsNullOrEmpty(Filename))
|
||||||
request.AddParameter(@"filename", Filename);
|
request.AddParameter(@"filename", Filename);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
{
|
{
|
||||||
public class GetFriendsRequest : APIRequest<List<APIUser>>
|
public class GetFriendsRequest : APIRequest<List<APIRelation>>
|
||||||
{
|
{
|
||||||
protected override string Target => @"friends";
|
protected override string Target => @"friends";
|
||||||
}
|
}
|
||||||
|
30
osu.Game/Online/API/Requests/Responses/APIRelation.cs
Normal file
30
osu.Game/Online/API/Requests/Responses/APIRelation.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
public class APIRelation
|
||||||
|
{
|
||||||
|
[JsonProperty("target_id")]
|
||||||
|
public int TargetID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("relation_type")]
|
||||||
|
public RelationType RelationType { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mutual")]
|
||||||
|
public bool Mutual { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("target")]
|
||||||
|
public APIUser? TargetUser { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public enum RelationType
|
||||||
|
{
|
||||||
|
Friend,
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Online.Chat
|
namespace osu.Game.Online.Chat
|
||||||
{
|
{
|
||||||
@ -60,12 +60,12 @@ namespace osu.Game.Online.Chat
|
|||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = @"Copy link",
|
Text = CommonStrings.CopyLink,
|
||||||
Action = copyExternalLinkAction
|
Action = copyExternalLinkAction
|
||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = CommonStrings.ButtonsCancel,
|
Text = WebCommonStrings.ButtonsCancel,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -33,6 +34,8 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
using CommonStrings = osu.Game.Localisation.CommonStrings;
|
||||||
|
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
@ -71,6 +74,12 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private SongSelect songSelect { get; set; }
|
private SongSelect songSelect { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Clipboard clipboard { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
public ITooltip<ScoreInfo> GetCustomTooltip() => new LeaderboardScoreTooltip();
|
public ITooltip<ScoreInfo> GetCustomTooltip() => new LeaderboardScoreTooltip();
|
||||||
public virtual ScoreInfo TooltipContent => Score;
|
public virtual ScoreInfo TooltipContent => Score;
|
||||||
|
|
||||||
@ -423,10 +432,13 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
if (Score.Mods.Length > 0 && songSelect != null)
|
if (Score.Mods.Length > 0 && songSelect != null)
|
||||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||||
|
|
||||||
|
if (Score.OnlineID > 0)
|
||||||
|
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.WebsiteRootUrl}/scores/{Score.OnlineID}")));
|
||||||
|
|
||||||
if (Score.Files.Count > 0)
|
if (Score.Files.Count > 0)
|
||||||
{
|
{
|
||||||
items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score)));
|
items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score)));
|
||||||
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
items.Add(new OsuMenuItem(WebCommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
|
@ -6,14 +6,17 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -35,7 +38,8 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
|
|
||||||
private CancellationTokenSource cancellationToken;
|
private CancellationTokenSource cancellationToken;
|
||||||
|
|
||||||
private Drawable currentContent;
|
[CanBeNull]
|
||||||
|
private SearchContainer currentContent;
|
||||||
|
|
||||||
private FriendOnlineStreamControl onlineStreamControl;
|
private FriendOnlineStreamControl onlineStreamControl;
|
||||||
private Box background;
|
private Box background;
|
||||||
@ -43,8 +47,9 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
private UserListToolbar userListToolbar;
|
private UserListToolbar userListToolbar;
|
||||||
private Container itemsPlaceholder;
|
private Container itemsPlaceholder;
|
||||||
private LoadingLayer loading;
|
private LoadingLayer loading;
|
||||||
|
private BasicSearchTextBox searchTextBox;
|
||||||
|
|
||||||
private readonly IBindableList<APIUser> apiFriends = new BindableList<APIUser>();
|
private readonly IBindableList<APIRelation> apiFriends = new BindableList<APIRelation>();
|
||||||
|
|
||||||
public FriendDisplay()
|
public FriendDisplay()
|
||||||
{
|
{
|
||||||
@ -104,7 +109,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
Margin = new MarginPadding { Bottom = 20 },
|
Margin = new MarginPadding { Bottom = 20 },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -113,11 +118,38 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
Horizontal = 40,
|
Horizontal = 40,
|
||||||
Vertical = 20
|
Vertical = 20
|
||||||
},
|
},
|
||||||
Child = userListToolbar = new UserListToolbar
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
new Dimension(),
|
||||||
Origin = Anchor.CentreRight,
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
}
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
searchTextBox = new BasicSearchTextBox
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Height = 40,
|
||||||
|
ReleaseFocusOnCommit = false,
|
||||||
|
HoldFocus = true,
|
||||||
|
PlaceholderText = HomeStrings.SearchPlaceholder,
|
||||||
|
},
|
||||||
|
Empty(),
|
||||||
|
userListToolbar = new UserListToolbar
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -145,7 +177,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
controlBackground.Colour = colourProvider.Background5;
|
controlBackground.Colour = colourProvider.Background5;
|
||||||
|
|
||||||
apiFriends.BindTo(api.Friends);
|
apiFriends.BindTo(api.Friends);
|
||||||
apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.ToList()), true);
|
apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.Select(f => f.TargetUser).ToList()), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -155,6 +187,11 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
onlineStreamControl.Current.BindValueChanged(_ => recreatePanels());
|
onlineStreamControl.Current.BindValueChanged(_ => recreatePanels());
|
||||||
userListToolbar.DisplayStyle.BindValueChanged(_ => recreatePanels());
|
userListToolbar.DisplayStyle.BindValueChanged(_ => recreatePanels());
|
||||||
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
||||||
|
searchTextBox.Current.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
if (currentContent.IsNotNull())
|
||||||
|
currentContent.SearchTerm = searchTextBox.Current.Value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recreatePanels()
|
private void recreatePanels()
|
||||||
@ -188,7 +225,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addContentToPlaceholder(Drawable content)
|
private void addContentToPlaceholder(SearchContainer content)
|
||||||
{
|
{
|
||||||
loading.Hide();
|
loading.Hide();
|
||||||
|
|
||||||
@ -204,16 +241,17 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
|||||||
currentContent.FadeIn(200, Easing.OutQuint);
|
currentContent.FadeIn(200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FillFlowContainer createTable(List<APIUser> users)
|
private SearchContainer createTable(List<APIUser> users)
|
||||||
{
|
{
|
||||||
var style = userListToolbar.DisplayStyle.Value;
|
var style = userListToolbar.DisplayStyle.Value;
|
||||||
|
|
||||||
return new FillFlowContainer
|
return new SearchContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2),
|
Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2),
|
||||||
Children = users.Select(u => createUserPanel(u, style)).ToList()
|
Children = users.Select(u => createUserPanel(u, style)).ToList(),
|
||||||
|
SearchTerm = searchTextBox.Current.Value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using SharpCompress;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
@ -13,15 +23,201 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||||
|
|
||||||
public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled;
|
// Because it is impossible to update the number of friends after the operation,
|
||||||
|
// the number of friends obtained is stored and modified locally.
|
||||||
|
private int followerCount;
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (status.Value)
|
||||||
|
{
|
||||||
|
case FriendStatus.Self:
|
||||||
|
return FriendsStrings.ButtonsDisabled;
|
||||||
|
|
||||||
|
case FriendStatus.None:
|
||||||
|
return FriendsStrings.ButtonsAdd;
|
||||||
|
|
||||||
|
case FriendStatus.NotMutual:
|
||||||
|
case FriendStatus.Mutual:
|
||||||
|
return FriendsStrings.ButtonsRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FriendsStrings.TitleCompact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override IconUsage Icon => FontAwesome.Solid.User;
|
protected override IconUsage Icon => FontAwesome.Solid.User;
|
||||||
|
|
||||||
|
private readonly IBindableList<APIRelation> apiFriends = new BindableList<APIRelation>();
|
||||||
|
private readonly IBindable<APIUser> localUser = new Bindable<APIUser>();
|
||||||
|
|
||||||
|
private readonly Bindable<FriendStatus> status = new Bindable<FriendStatus>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colour { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(IAPIProvider api, INotificationOverlay? notifications)
|
||||||
{
|
{
|
||||||
// todo: when friending/unfriending is implemented, the APIAccess.Friends list should be updated accordingly.
|
localUser.BindTo(api.LocalUser);
|
||||||
User.BindValueChanged(user => SetValue(user.NewValue?.User.FollowerCount ?? 0), true);
|
|
||||||
|
status.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateIcon();
|
||||||
|
updateColor();
|
||||||
|
});
|
||||||
|
|
||||||
|
User.BindValueChanged(u =>
|
||||||
|
{
|
||||||
|
followerCount = u.NewValue?.User.FollowerCount ?? 0;
|
||||||
|
updateStatus();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
apiFriends.BindTo(api.Friends);
|
||||||
|
apiFriends.BindCollectionChanged((_, _) => Schedule(updateStatus));
|
||||||
|
|
||||||
|
Action += () =>
|
||||||
|
{
|
||||||
|
if (User.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (status.Value == FriendStatus.Self)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ShowLoadingLayer();
|
||||||
|
|
||||||
|
APIRequest req = status.Value == FriendStatus.None ? new AddFriendRequest(User.Value.User.OnlineID) : new DeleteFriendRequest(User.Value.User.OnlineID);
|
||||||
|
|
||||||
|
req.Success += () =>
|
||||||
|
{
|
||||||
|
if (req is AddFriendRequest addedRequest)
|
||||||
|
{
|
||||||
|
SetValue(++followerCount);
|
||||||
|
status.Value = addedRequest.Response?.UserRelation.Mutual == true ? FriendStatus.Mutual : FriendStatus.NotMutual;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetValue(--followerCount);
|
||||||
|
status.Value = FriendStatus.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.UpdateLocalFriends();
|
||||||
|
HideLoadingLayer();
|
||||||
|
};
|
||||||
|
|
||||||
|
req.Failure += e =>
|
||||||
|
{
|
||||||
|
notifications?.Post(new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = e.Message,
|
||||||
|
Icon = FontAwesome.Solid.Times,
|
||||||
|
});
|
||||||
|
|
||||||
|
HideLoadingLayer();
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(req);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
if (status.Value > FriendStatus.None)
|
||||||
|
{
|
||||||
|
SetIcon(FontAwesome.Solid.UserTimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
|
||||||
|
updateIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatus()
|
||||||
|
{
|
||||||
|
SetValue(followerCount);
|
||||||
|
|
||||||
|
if (localUser.Value.OnlineID == User.Value?.User.OnlineID)
|
||||||
|
{
|
||||||
|
status.Value = FriendStatus.Self;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var friend = apiFriends.FirstOrDefault(u => User.Value?.User.OnlineID == u.TargetID);
|
||||||
|
|
||||||
|
if (friend != null)
|
||||||
|
{
|
||||||
|
status.Value = friend.Mutual ? FriendStatus.Mutual : FriendStatus.NotMutual;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status.Value = FriendStatus.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIcon()
|
||||||
|
{
|
||||||
|
switch (status.Value)
|
||||||
|
{
|
||||||
|
case FriendStatus.Self:
|
||||||
|
SetIcon(FontAwesome.Solid.User);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FriendStatus.None:
|
||||||
|
SetIcon(FontAwesome.Solid.UserPlus);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FriendStatus.NotMutual:
|
||||||
|
SetIcon(FontAwesome.Solid.User);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FriendStatus.Mutual:
|
||||||
|
SetIcon(FontAwesome.Solid.UserFriends);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColor()
|
||||||
|
{
|
||||||
|
// https://github.com/ppy/osu-web/blob/0a5367a4a68a6cdf450eb483251b3cf03b3ac7d2/resources/css/bem/user-action-button.less
|
||||||
|
|
||||||
|
switch (status.Value)
|
||||||
|
{
|
||||||
|
case FriendStatus.Self:
|
||||||
|
case FriendStatus.None:
|
||||||
|
IdleColour = colourProvider.Background6;
|
||||||
|
HoverColour = colourProvider.Background5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FriendStatus.NotMutual:
|
||||||
|
IdleColour = colour.Green.Opacity(0.7f);
|
||||||
|
HoverColour = IdleColour.Lighten(0.1f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FriendStatus.Mutual:
|
||||||
|
IdleColour = colour.Pink.Opacity(0.7f);
|
||||||
|
HoverColour = IdleColour.Lighten(0.1f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EffectTargets.ForEach(d => d.FadeColour(IsHovered ? HoverColour : IdleColour, FADE_DURATION, Easing.OutQuint));
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FriendStatus
|
||||||
|
{
|
||||||
|
Self,
|
||||||
|
None,
|
||||||
|
NotMutual,
|
||||||
|
Mutual,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
@ -14,6 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
private readonly LoadingLayer loading;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
@ -40,11 +42,22 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = 10 },
|
Padding = new MarginPadding { Horizontal = 10 },
|
||||||
}
|
},
|
||||||
|
loading = new LoadingLayer(true, false)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void ShowLoadingLayer()
|
||||||
|
{
|
||||||
|
loading.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void HideLoadingLayer()
|
||||||
|
{
|
||||||
|
loading.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
public abstract partial class ProfileHeaderStatisticsButton : ProfileHeaderButton
|
public abstract partial class ProfileHeaderStatisticsButton : ProfileHeaderButton
|
||||||
{
|
{
|
||||||
private readonly OsuSpriteText drawableText;
|
private readonly OsuSpriteText drawableText;
|
||||||
|
private readonly Container iconContainer;
|
||||||
|
|
||||||
protected ProfileHeaderStatisticsButton()
|
protected ProfileHeaderStatisticsButton()
|
||||||
{
|
{
|
||||||
@ -26,13 +27,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SpriteIcon
|
iconContainer = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Icon = Icon,
|
AutoSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fit,
|
|
||||||
Size = new Vector2(50, 14)
|
|
||||||
},
|
},
|
||||||
drawableText = new OsuSpriteText
|
drawableText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
@ -43,10 +42,24 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SetIcon(Icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IconUsage Icon { get; }
|
protected abstract IconUsage Icon { get; }
|
||||||
|
|
||||||
|
protected void SetIcon(IconUsage icon)
|
||||||
|
{
|
||||||
|
iconContainer.Child = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Icon = icon,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
Size = new Vector2(50, 14)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected void SetValue(int value) => drawableText.Text = value.ToLocalisableString("#,##0");
|
protected void SetValue(int value) => drawableText.Text = value.ToLocalisableString("#,##0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
var button = buttons[i++];
|
var button = buttons[i++];
|
||||||
button.UpdateKeyCombination(d);
|
button.UpdateKeyCombination(d);
|
||||||
|
|
||||||
tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false);
|
tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false, restoringDefaults: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefault.Value = true;
|
isDefault.Value = true;
|
||||||
@ -489,12 +489,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
base.OnFocusLost(e);
|
base.OnFocusLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding)
|
private bool isConflictingBinding(RealmKeyBinding first, RealmKeyBinding second, bool restoringDefaults)
|
||||||
|
{
|
||||||
|
if (first.ID == second.ID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// ignore conflicts with same action bindings during revert. the assumption is that the other binding will be reverted subsequently in the same higher-level operation.
|
||||||
|
// this happens if the bindings for an action are rebound to the same keys, but the ordering of the bindings itself is different.
|
||||||
|
if (restoringDefaults && first.ActionInt == second.ActionInt)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return first.KeyCombination.Equals(second.KeyCombination);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding, bool restoringDefaults = false)
|
||||||
{
|
{
|
||||||
List<RealmKeyBinding> bindings = GetAllSectionBindings();
|
List<RealmKeyBinding> bindings = GetAllSectionBindings();
|
||||||
RealmKeyBinding? existingBinding = keyBinding.KeyCombination.Equals(new KeyCombination(InputKey.None))
|
RealmKeyBinding? existingBinding = keyBinding.KeyCombination.Equals(new KeyCombination(InputKey.None))
|
||||||
? null
|
? null
|
||||||
: bindings.FirstOrDefault(other => other.ID != keyBinding.ID && other.KeyCombination.Equals(keyBinding.KeyCombination));
|
: bindings.FirstOrDefault(other => isConflictingBinding(keyBinding, other, restoringDefaults));
|
||||||
|
|
||||||
if (existingBinding == null)
|
if (existingBinding == null)
|
||||||
{
|
{
|
||||||
|
@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
|
protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
|
||||||
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
|
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
|
||||||
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
|
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
|
||||||
|
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mods which were applied to the beatmap.
|
/// The mods which were applied to the beatmap.
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
@ -12,6 +16,15 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
protected override double HoverExpansionDelay => 250;
|
protected override double HoverExpansionDelay => 250;
|
||||||
|
|
||||||
|
protected override bool ExpandOnHover => expandOnHover;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> contractSidebars = new Bindable<bool>();
|
||||||
|
|
||||||
|
private bool expandOnHover;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Editor? editor { get; set; }
|
||||||
|
|
||||||
public ExpandingToolboxContainer(float contractedWidth, float expandedWidth)
|
public ExpandingToolboxContainer(float contractedWidth, float expandedWidth)
|
||||||
: base(contractedWidth, expandedWidth)
|
: base(contractedWidth, expandedWidth)
|
||||||
{
|
{
|
||||||
@ -19,6 +32,27 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
FillFlow.Spacing = new Vector2(5);
|
FillFlow.Spacing = new Vector2(5);
|
||||||
FillFlow.Padding = new MarginPadding { Vertical = 5 };
|
FillFlow.Padding = new MarginPadding { Vertical = 5 };
|
||||||
|
|
||||||
|
Expanded.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.EditorContractSidebars, contractSidebars);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
bool requireContracting = contractSidebars.Value || editor?.DrawSize.X / editor?.DrawSize.Y < 1.7f;
|
||||||
|
|
||||||
|
if (expandOnHover != requireContracting)
|
||||||
|
{
|
||||||
|
expandOnHover = requireContracting;
|
||||||
|
Expanded.Value = !expandOnHover;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
||||||
|
@ -344,8 +344,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
PlayfieldContentContainer.Anchor = Anchor.CentreLeft;
|
PlayfieldContentContainer.Anchor = Anchor.CentreLeft;
|
||||||
PlayfieldContentContainer.Origin = Anchor.CentreLeft;
|
PlayfieldContentContainer.Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT);
|
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth);
|
||||||
PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT;
|
PlayfieldContentContainer.X = LeftToolbox.DrawWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position)
|
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position)
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets
|
|||||||
/// <param name="acronym">The acronym to query for .</param>
|
/// <param name="acronym">The acronym to query for .</param>
|
||||||
public Mod? CreateModFromAcronym(string acronym)
|
public Mod? CreateModFromAcronym(string acronym)
|
||||||
{
|
{
|
||||||
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
|
return AllMods.FirstOrDefault(m => string.Equals(m.Acronym, acronym, StringComparison.OrdinalIgnoreCase))?.CreateInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
12
osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs
Normal file
12
osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// 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.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
public enum EditorOrigin
|
||||||
|
{
|
||||||
|
GridCentre,
|
||||||
|
PlayfieldCentre,
|
||||||
|
SelectionCentre
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
@ -27,6 +28,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private readonly BindableList<BreakPeriod> breaks = new BindableList<BreakPeriod>();
|
private readonly BindableList<BreakPeriod> breaks = new BindableList<BreakPeriod>();
|
||||||
|
|
||||||
|
private readonly BindableBool showBreaks = new BindableBool(true);
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager configManager)
|
||||||
|
{
|
||||||
|
configManager.BindWith(OsuSetting.EditorTimelineShowBreaks, showBreaks);
|
||||||
|
showBreaks.BindValueChanged(_ => breakCache.Invalidate());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
||||||
{
|
{
|
||||||
base.LoadBeatmap(beatmap);
|
base.LoadBeatmap(beatmap);
|
||||||
@ -67,6 +77,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
|
if (!showBreaks.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < breaks.Count; i++)
|
for (int i = 0; i < breaks.Count; i++)
|
||||||
{
|
{
|
||||||
var breakPeriod = breaks[i];
|
var breakPeriod = breaks[i];
|
||||||
|
@ -214,7 +214,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
private Bindable<bool> editorAutoSeekOnPlacement;
|
private Bindable<bool> editorAutoSeekOnPlacement;
|
||||||
private Bindable<bool> editorLimitedDistanceSnap;
|
private Bindable<bool> editorLimitedDistanceSnap;
|
||||||
private Bindable<bool> editorTimelineShowTimingChanges;
|
private Bindable<bool> editorTimelineShowTimingChanges;
|
||||||
|
private Bindable<bool> editorTimelineShowBreaks;
|
||||||
private Bindable<bool> editorTimelineShowTicks;
|
private Bindable<bool> editorTimelineShowTicks;
|
||||||
|
private Bindable<bool> editorContractSidebars;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This controls the opacity of components like the timelines, sidebars, etc.
|
/// This controls the opacity of components like the timelines, sidebars, etc.
|
||||||
@ -322,7 +324,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
editorAutoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
editorAutoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
||||||
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
editorTimelineShowTimingChanges = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
editorTimelineShowTimingChanges = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
||||||
|
editorTimelineShowBreaks = config.GetBindable<bool>(OsuSetting.EditorTimelineShowBreaks);
|
||||||
editorTimelineShowTicks = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
editorTimelineShowTicks = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
||||||
|
editorContractSidebars = config.GetBindable<bool>(OsuSetting.EditorContractSidebars);
|
||||||
|
|
||||||
AddInternal(new OsuContextMenuContainer
|
AddInternal(new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
@ -388,6 +392,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
State = { BindTarget = editorTimelineShowTicks }
|
State = { BindTarget = editorTimelineShowTicks }
|
||||||
},
|
},
|
||||||
|
new ToggleMenuItem(EditorStrings.TimelineShowBreaks)
|
||||||
|
{
|
||||||
|
State = { BindTarget = editorTimelineShowBreaks }
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
new BackgroundDimMenuItem(editorBackgroundDim),
|
new BackgroundDimMenuItem(editorBackgroundDim),
|
||||||
@ -402,7 +410,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
new ToggleMenuItem(EditorStrings.LimitedDistanceSnap)
|
new ToggleMenuItem(EditorStrings.LimitedDistanceSnap)
|
||||||
{
|
{
|
||||||
State = { BindTarget = editorLimitedDistanceSnap },
|
State = { BindTarget = editorLimitedDistanceSnap },
|
||||||
}
|
},
|
||||||
|
new ToggleMenuItem(EditorStrings.ContractSidebars)
|
||||||
|
{
|
||||||
|
State = { BindTarget = editorContractSidebars }
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuItem(EditorStrings.Timing)
|
new MenuItem(EditorStrings.Timing)
|
||||||
|
@ -316,7 +316,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
HasQuit.BindValueChanged(_ => updateState());
|
HasQuit.BindValueChanged(_ => updateState());
|
||||||
|
|
||||||
isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.Id);
|
isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.TargetID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -562,11 +562,8 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
catch (BeatmapInvalidForRulesetException)
|
catch (BeatmapInvalidForRulesetException)
|
||||||
{
|
{
|
||||||
// A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset
|
Logger.Log($"The current beatmap is not playable in {ruleset.RulesetInfo.Name}!", level: LogLevel.Important);
|
||||||
rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset;
|
return null;
|
||||||
ruleset = rulesetInfo.CreateInstance();
|
|
||||||
|
|
||||||
playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, gameplayMods, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playable.HitObjects.Count == 0)
|
if (playable.HitObjects.Count == 0)
|
||||||
|
@ -32,6 +32,8 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using CommonStrings = osu.Game.Localisation.CommonStrings;
|
||||||
|
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
@ -296,10 +298,10 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||||
|
|
||||||
if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url)
|
if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url)
|
||||||
items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
||||||
|
|
||||||
if (hideRequested != null)
|
if (hideRequested != null)
|
||||||
items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
items.Add(new OsuMenuItem(WebCommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -300,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
|
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
|
||||||
|
|
||||||
if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url)
|
if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url)
|
||||||
items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
||||||
|
|
||||||
if (dialogOverlay != null)
|
if (dialogOverlay != null)
|
||||||
items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
|
items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -23,6 +24,7 @@ using osu.Game.Graphics.Backgrounds;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -82,6 +84,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreManager scoreManager { get; set; } = null!;
|
private ScoreManager scoreManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Clipboard? clipboard { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
private Container content = null!;
|
private Container content = null!;
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
private Box foreground = null!;
|
private Box foreground = null!;
|
||||||
@ -769,6 +777,9 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
if (score.Mods.Length > 0)
|
if (score.Mods.Length > 0)
|
||||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray()));
|
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray()));
|
||||||
|
|
||||||
|
if (score.OnlineID > 0)
|
||||||
|
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.WebsiteRootUrl}/scores/{score.OnlineID}")));
|
||||||
|
|
||||||
if (score.Files.Count <= 0) return items.ToArray();
|
if (score.Files.Count <= 0) return items.ToArray();
|
||||||
|
|
||||||
items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score)));
|
items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score)));
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -18,6 +20,9 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Localisation.SkinComponents;
|
using osu.Game.Localisation.SkinComponents;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Skinning.Components
|
namespace osu.Game.Skinning.Components
|
||||||
{
|
{
|
||||||
@ -33,7 +38,20 @@ namespace osu.Game.Skinning.Components
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||||
|
|
||||||
private readonly OsuSpriteText text;
|
private readonly OsuSpriteText text;
|
||||||
|
private IBindable<StarDifficulty?>? difficultyBindable;
|
||||||
|
private CancellationTokenSource? difficultyCancellationSource;
|
||||||
|
private ModSettingChangeTracker? modSettingTracker;
|
||||||
|
private StarDifficulty? starDifficulty;
|
||||||
|
|
||||||
public BeatmapAttributeText()
|
public BeatmapAttributeText()
|
||||||
{
|
{
|
||||||
@ -55,7 +73,35 @@ namespace osu.Game.Skinning.Components
|
|||||||
|
|
||||||
Attribute.BindValueChanged(_ => updateText());
|
Attribute.BindValueChanged(_ => updateText());
|
||||||
Template.BindValueChanged(_ => updateText());
|
Template.BindValueChanged(_ => updateText());
|
||||||
beatmap.BindValueChanged(_ => updateText());
|
|
||||||
|
beatmap.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
difficultyCancellationSource?.Cancel();
|
||||||
|
difficultyCancellationSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
difficultyBindable?.UnbindAll();
|
||||||
|
difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token);
|
||||||
|
difficultyBindable.BindValueChanged(d =>
|
||||||
|
{
|
||||||
|
starDifficulty = d.NewValue;
|
||||||
|
updateText();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateText();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
mods.BindValueChanged(m =>
|
||||||
|
{
|
||||||
|
modSettingTracker?.Dispose();
|
||||||
|
modSettingTracker = new ModSettingChangeTracker(m.NewValue)
|
||||||
|
{
|
||||||
|
SettingChanged = _ => updateText()
|
||||||
|
};
|
||||||
|
|
||||||
|
updateText();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
ruleset.BindValueChanged(_ => updateText());
|
||||||
|
|
||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
@ -156,37 +202,64 @@ namespace osu.Game.Skinning.Components
|
|||||||
return beatmap.Value.BeatmapInfo.Metadata.Source;
|
return beatmap.Value.BeatmapInfo.Metadata.Source;
|
||||||
|
|
||||||
case BeatmapAttribute.Length:
|
case BeatmapAttribute.Length:
|
||||||
return TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration();
|
return Math.Round(beatmap.Value.BeatmapInfo.Length / ModUtils.CalculateRateWithMods(mods.Value)).ToFormattedDuration();
|
||||||
|
|
||||||
case BeatmapAttribute.RankedStatus:
|
case BeatmapAttribute.RankedStatus:
|
||||||
return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription();
|
return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription();
|
||||||
|
|
||||||
case BeatmapAttribute.BPM:
|
case BeatmapAttribute.BPM:
|
||||||
return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2");
|
return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"F2");
|
||||||
|
|
||||||
case BeatmapAttribute.CircleSize:
|
case BeatmapAttribute.CircleSize:
|
||||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2");
|
return computeDifficulty().CircleSize.ToLocalisableString(@"F2");
|
||||||
|
|
||||||
case BeatmapAttribute.HPDrain:
|
case BeatmapAttribute.HPDrain:
|
||||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2");
|
return computeDifficulty().DrainRate.ToLocalisableString(@"F2");
|
||||||
|
|
||||||
case BeatmapAttribute.Accuracy:
|
case BeatmapAttribute.Accuracy:
|
||||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2");
|
return computeDifficulty().OverallDifficulty.ToLocalisableString(@"F2");
|
||||||
|
|
||||||
case BeatmapAttribute.ApproachRate:
|
case BeatmapAttribute.ApproachRate:
|
||||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2");
|
return computeDifficulty().ApproachRate.ToLocalisableString(@"F2");
|
||||||
|
|
||||||
case BeatmapAttribute.StarRating:
|
case BeatmapAttribute.StarRating:
|
||||||
return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2");
|
return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BeatmapDifficulty computeDifficulty()
|
||||||
|
{
|
||||||
|
BeatmapDifficulty difficulty = new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty);
|
||||||
|
|
||||||
|
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
|
||||||
|
mod.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
if (ruleset.Value is RulesetInfo rulesetInfo)
|
||||||
|
{
|
||||||
|
double rate = ModUtils.CalculateRateWithMods(mods.Value);
|
||||||
|
difficulty = rulesetInfo.CreateInstance().GetRateAdjustedDisplayDifficulty(difficulty, rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return difficulty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40);
|
protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40);
|
||||||
|
|
||||||
protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour;
|
protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
difficultyCancellationSource?.Cancel();
|
||||||
|
difficultyCancellationSource?.Dispose();
|
||||||
|
difficultyCancellationSource = null;
|
||||||
|
|
||||||
|
modSettingTracker?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END.
|
// WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END.
|
||||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
|
|
||||||
case GetBeatmapRequest getBeatmapRequest:
|
case GetBeatmapRequest getBeatmapRequest:
|
||||||
{
|
{
|
||||||
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single());
|
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.OnlineID).Single());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -13,6 +14,7 @@ using osu.Game.Overlays;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -28,7 +30,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Users
|
namespace osu.Game.Users
|
||||||
{
|
{
|
||||||
public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu
|
public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu, IFilterable
|
||||||
{
|
{
|
||||||
public readonly APIUser User;
|
public readonly APIUser User;
|
||||||
|
|
||||||
@ -162,5 +164,20 @@ namespace osu.Game.Users
|
|||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<LocalisableString> FilterTerms => [User.Username];
|
||||||
|
|
||||||
|
public bool MatchingFilter
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
Show();
|
||||||
|
else
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilteringActive { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.1025.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" />
|
||||||
<PackageReference Include="Sentry" Version="4.12.1" />
|
<PackageReference Include="Sentry" Version="4.12.1" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
|
@ -17,6 +17,6 @@
|
|||||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1025.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user