mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge branch 'master' into realm-key-binding-store
This commit is contained in:
commit
acc06ca398
@ -33,10 +33,10 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2021.524.0",
|
||||
"version": "2021.608.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.609.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.611.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.611.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,2 @@
|
||||
[General]
|
||||
// no version specified means v1
|
@ -8,9 +8,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
Fruit,
|
||||
Banana,
|
||||
Droplet,
|
||||
CatcherIdle,
|
||||
CatcherFail,
|
||||
CatcherKiai,
|
||||
Catcher,
|
||||
CatchComboCounter
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double ApproachRate;
|
||||
public double ApproachRate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -39,10 +39,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
// We are heavily relying on aim in catch the beat
|
||||
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
||||
|
||||
|
@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
|
||||
{
|
||||
public override double ScoreMultiplier => 1.12;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
|
||||
}
|
||||
|
@ -33,13 +33,13 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||
{
|
||||
private readonly Catcher catcher;
|
||||
private readonly CatcherArea catcherArea;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
public MouseInputHelper(CatchPlayfield playfield)
|
||||
{
|
||||
catcher = playfield.CatcherArea.MovableCatcher;
|
||||
catcherArea = playfield.CatcherArea;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||
catcherArea.SetCatcherPosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
}
|
||||
|
54
osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
Normal file
54
osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||
{
|
||||
public class DefaultCatcher : CompositeDrawable, ICatcherSprite
|
||||
{
|
||||
public Bindable<CatcherAnimationState> CurrentState { get; } = new Bindable<CatcherAnimationState>();
|
||||
|
||||
public Texture CurrentTexture => sprite.Texture;
|
||||
|
||||
private readonly Sprite sprite;
|
||||
|
||||
private readonly Dictionary<CatcherAnimationState, Texture> textures = new Dictionary<CatcherAnimationState, Texture>();
|
||||
|
||||
public DefaultCatcher()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
InternalChild = sprite = new Sprite
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore store, Bindable<CatcherAnimationState> currentState)
|
||||
{
|
||||
CurrentState.BindTo(currentState);
|
||||
|
||||
textures[CatcherAnimationState.Idle] = store.Get(@"Gameplay/catch/fruit-catcher-idle");
|
||||
textures[CatcherAnimationState.Fail] = store.Get(@"Gameplay/catch/fruit-catcher-fail");
|
||||
textures[CatcherAnimationState.Kiai] = store.Get(@"Gameplay/catch/fruit-catcher-kiai");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
CurrentState.BindValueChanged(state => sprite.Texture = textures[state.NewValue], true);
|
||||
}
|
||||
}
|
||||
}
|
12
osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs
Normal file
12
osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.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.
|
||||
|
||||
using osu.Framework.Graphics.Textures;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
public interface ICatcherSprite
|
||||
{
|
||||
Texture CurrentTexture { get; }
|
||||
}
|
||||
}
|
@ -65,17 +65,21 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.Catcher:
|
||||
var version = Source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
if (version < 2.3m)
|
||||
{
|
||||
if (GetTexture(@"fruit-ryuuta") != null ||
|
||||
GetTexture(@"fruit-ryuuta-0") != null)
|
||||
return new LegacyCatcherOld();
|
||||
}
|
||||
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
if (GetTexture(@"fruit-catcher-idle") != null ||
|
||||
GetTexture(@"fruit-catcher-idle-0") != null)
|
||||
return new LegacyCatcherNew();
|
||||
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
|
73
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
Normal file
73
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
public class LegacyCatcherNew : CompositeDrawable, ICatcherSprite
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<CatcherAnimationState> currentState { get; set; }
|
||||
|
||||
public Texture CurrentTexture => (currentDrawable as TextureAnimation)?.CurrentFrame ?? (currentDrawable as Sprite)?.Texture;
|
||||
|
||||
private readonly Dictionary<CatcherAnimationState, Drawable> drawables = new Dictionary<CatcherAnimationState, Drawable>();
|
||||
|
||||
private Drawable currentDrawable;
|
||||
|
||||
public LegacyCatcherNew()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast<CatcherAnimationState>())
|
||||
{
|
||||
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.Origin = Anchor.TopCentre;
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Size = Vector2.One;
|
||||
d.FillMode = FillMode.Fit;
|
||||
d.Alpha = 0;
|
||||
}));
|
||||
}
|
||||
|
||||
currentDrawable = drawables[CatcherAnimationState.Idle];
|
||||
|
||||
Drawable getDrawableFor(CatcherAnimationState state) =>
|
||||
skin.GetAnimation(@$"fruit-catcher-{state.ToString().ToLowerInvariant()}", true, true, true) ??
|
||||
skin.GetAnimation(@"fruit-catcher-idle", true, true, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
currentState.BindValueChanged(state =>
|
||||
{
|
||||
currentDrawable.Alpha = 0;
|
||||
currentDrawable = drawables[state.NewValue];
|
||||
currentDrawable.Alpha = 1;
|
||||
|
||||
(currentDrawable as IFramedAnimation)?.GotoFrame(0);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
37
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
Normal file
37
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
public class LegacyCatcherOld : CompositeDrawable, ICatcherSprite
|
||||
{
|
||||
public Texture CurrentTexture => (InternalChild as TextureAnimation)?.CurrentFrame ?? (InternalChild as Sprite)?.Texture;
|
||||
|
||||
public LegacyCatcherOld()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true).With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.Origin = Anchor.TopCentre;
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Size = Vector2.One;
|
||||
d.FillMode = FillMode.Fit;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -7,10 +7,9 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
@ -18,6 +17,7 @@ using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -25,7 +25,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
||||
public class Catcher : SkinReloadableDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
||||
@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
public const double BASE_SPEED = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// The current speed of the catcher.
|
||||
/// </summary>
|
||||
public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier;
|
||||
|
||||
/// <summary>
|
||||
/// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught".
|
||||
/// </summary>
|
||||
@ -78,24 +83,24 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
private readonly Container<CaughtObject> droppedObjectTarget;
|
||||
|
||||
public CatcherAnimationState CurrentState { get; private set; }
|
||||
[Cached]
|
||||
protected readonly Bindable<CatcherAnimationState> CurrentStateBindable = new Bindable<CatcherAnimationState>();
|
||||
|
||||
public CatcherAnimationState CurrentState => CurrentStateBindable.Value;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
||||
/// </summary>
|
||||
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// The drawable catcher for <see cref="CurrentState"/>.
|
||||
/// </summary>
|
||||
internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
|
||||
internal Texture CurrentTexture => ((ICatcherSprite)currentCatcher.Drawable).CurrentTexture;
|
||||
|
||||
private bool dashing;
|
||||
|
||||
public bool Dashing
|
||||
{
|
||||
get => dashing;
|
||||
protected set
|
||||
set
|
||||
{
|
||||
if (value == dashing) return;
|
||||
|
||||
@ -105,22 +110,22 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
public Direction VisualDirection
|
||||
{
|
||||
get => Scale.X > 0 ? Direction.Right : Direction.Left;
|
||||
set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
private readonly float catchWidth;
|
||||
|
||||
private readonly CatcherSprite catcherIdle;
|
||||
private readonly CatcherSprite catcherKiai;
|
||||
private readonly CatcherSprite catcherFail;
|
||||
|
||||
private CatcherSprite currentCatcher;
|
||||
private readonly SkinnableDrawable currentCatcher;
|
||||
|
||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
private int currentDirection;
|
||||
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
@ -156,20 +161,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||
currentCatcher = new SkinnableDrawable(
|
||||
new CatchSkinComponent(CatchSkinComponents.Catcher),
|
||||
_ => new DefaultCatcher())
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
catcherFail = new CatcherSprite(CatcherAnimationState.Fail)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE
|
||||
},
|
||||
hitExplosionContainer = new HitExplosionContainer
|
||||
{
|
||||
@ -184,8 +181,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||
trails = new CatcherTrailDisplay(this);
|
||||
|
||||
updateCatcher();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -328,55 +323,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePosition(float position)
|
||||
{
|
||||
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
||||
|
||||
if (position == X)
|
||||
return;
|
||||
|
||||
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
||||
X = position;
|
||||
}
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection--;
|
||||
return true;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection++;
|
||||
return true;
|
||||
|
||||
case CatchAction.Dash:
|
||||
Dashing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection++;
|
||||
break;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection--;
|
||||
break;
|
||||
|
||||
case CatchAction.Dash:
|
||||
Dashing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop any fruit off the plate.
|
||||
/// </summary>
|
||||
@ -418,15 +364,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (currentDirection == 0) return;
|
||||
|
||||
var direction = Math.Sign(currentDirection);
|
||||
|
||||
var dashModifier = Dashing ? 1 : 0.5;
|
||||
var speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
||||
|
||||
UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
|
||||
|
||||
// Correct overshooting.
|
||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
|
||||
@ -436,36 +373,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCatcher()
|
||||
{
|
||||
currentCatcher?.Hide();
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
default:
|
||||
currentCatcher = catcherIdle;
|
||||
break;
|
||||
|
||||
case CatcherAnimationState.Fail:
|
||||
currentCatcher = catcherFail;
|
||||
break;
|
||||
|
||||
case CatcherAnimationState.Kiai:
|
||||
currentCatcher = catcherKiai;
|
||||
break;
|
||||
}
|
||||
|
||||
currentCatcher.Show();
|
||||
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
|
||||
}
|
||||
|
||||
private void updateState(CatcherAnimationState state)
|
||||
{
|
||||
if (CurrentState == state)
|
||||
return;
|
||||
|
||||
CurrentState = state;
|
||||
updateCatcher();
|
||||
CurrentStateBindable.Value = state;
|
||||
}
|
||||
|
||||
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
@ -14,13 +16,20 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class CatcherArea : Container
|
||||
public class CatcherArea : Container, IKeyBindingHandler<CatchAction>
|
||||
{
|
||||
public const float CATCHER_SIZE = 106.75f;
|
||||
|
||||
public readonly Catcher MovableCatcher;
|
||||
private readonly CatchComboDisplay comboDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// <c>-1</c> when only left button is pressed.
|
||||
/// <c>1</c> when only right button is pressed.
|
||||
/// <c>0</c> when none or both left and right buttons are pressed.
|
||||
/// </summary>
|
||||
private int currentDirection;
|
||||
|
||||
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||
{
|
||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||
@ -63,16 +72,73 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
MovableCatcher.OnRevertResult(hitObject, result);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
SetCatcherPosition(
|
||||
replayState?.CatcherX ??
|
||||
(float)(MovableCatcher.X + MovableCatcher.Speed * currentDirection * Clock.ElapsedFrameTime));
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
if (state?.CatcherX != null)
|
||||
MovableCatcher.X = state.CatcherX.Value;
|
||||
|
||||
comboDisplay.X = MovableCatcher.X;
|
||||
}
|
||||
|
||||
public void SetCatcherPosition(float X)
|
||||
{
|
||||
float lastPosition = MovableCatcher.X;
|
||||
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
|
||||
|
||||
MovableCatcher.X = newPosition;
|
||||
|
||||
if (lastPosition < newPosition)
|
||||
MovableCatcher.VisualDirection = Direction.Right;
|
||||
else if (lastPosition > newPosition)
|
||||
MovableCatcher.VisualDirection = Direction.Left;
|
||||
}
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection--;
|
||||
return true;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection++;
|
||||
return true;
|
||||
|
||||
case CatchAction.Dash:
|
||||
MovableCatcher.Dashing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection++;
|
||||
break;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection--;
|
||||
break;
|
||||
|
||||
case CatchAction.Dash:
|
||||
MovableCatcher.Dashing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class CatcherSprite : SkinnableDrawable
|
||||
{
|
||||
protected override bool ApplySizeRestrictionsToDefault => true;
|
||||
|
||||
public CatcherSprite(CatcherAnimationState state)
|
||||
: base(new CatchSkinComponent(componentFromState(state)), _ =>
|
||||
new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
||||
|
||||
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
|
||||
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
|
||||
}
|
||||
|
||||
private static CatchSkinComponents componentFromState(CatcherAnimationState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CatcherAnimationState.Fail:
|
||||
return CatchSkinComponents.CatcherFail;
|
||||
|
||||
case CatcherAnimationState.Kiai:
|
||||
return CatchSkinComponents.CatcherKiai;
|
||||
|
||||
default:
|
||||
return CatchSkinComponents.CatcherIdle;
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultCatcherSprite : Sprite
|
||||
{
|
||||
private readonly CatcherAnimationState state;
|
||||
|
||||
public DefaultCatcherSprite(CatcherAnimationState state)
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
{
|
||||
Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,8 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -120,11 +118,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private CatcherTrailSprite createTrailSprite(Container<CatcherTrailSprite> target)
|
||||
{
|
||||
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
|
||||
|
||||
CatcherTrailSprite sprite = trailPool.Get();
|
||||
|
||||
sprite.Texture = texture;
|
||||
sprite.Texture = catcher.CurrentTexture;
|
||||
sprite.Anchor = catcher.Anchor;
|
||||
sprite.Scale = catcher.Scale;
|
||||
sprite.Blending = BlendingParameters.Additive;
|
||||
|
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
Right = 1,
|
||||
Left = -1
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double GreatHitWindow;
|
||||
public double ScoreMultiplier;
|
||||
public double GreatHitWindow { get; set; }
|
||||
public double ScoreMultiplier { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +44,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
|
||||
|
||||
double scoreMultiplier = 1.0;
|
||||
|
@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public abstract int KeyCount { get; }
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
||||
{
|
||||
|
@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
public override bool Ranked => false;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
{
|
||||
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||
|
@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override string Description => "Notes are flipped horizontally.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
[General]
|
||||
Version: 1.0
|
||||
// no version specified means v1
|
||||
|
||||
[Fonts]
|
||||
HitCircleOverlap: 3
|
||||
ScoreOverlap: 3
|
||||
ScoreOverlap: 3
|
||||
|
@ -7,11 +7,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double AimStrain;
|
||||
public double SpeedStrain;
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int HitCircleCount;
|
||||
public int SpinnerCount;
|
||||
public double AimStrain { get; set; }
|
||||
public double SpeedStrain { get; set; }
|
||||
public double ApproachRate { get; set; }
|
||||
public double OverallDifficulty { get; set; }
|
||||
public int HitCircleCount { get; set; }
|
||||
public int SpinnerCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -41,10 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
|
@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
|
||||
public override bool Ranked => false;
|
||||
|
||||
public override double ScoreMultiplier => 1.12;
|
||||
private DrawableOsuBlinds blinds;
|
||||
|
||||
|
@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
||||
{
|
||||
public override double ScoreMultiplier => 1.06;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToHitObject(HitObject hitObject)
|
||||
{
|
||||
|
@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
||||
{
|
||||
public override string Description => "It never gets boring!";
|
||||
public override bool Ranked => false;
|
||||
|
||||
// The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle.
|
||||
// The closer the hit objects draw to the border, the sharper the turn
|
||||
|
@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Automation;
|
||||
public override string Description => @"Spinners will be automatically completed.";
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
public override bool Ranked => true;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
|
||||
|
||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
|
@ -13,7 +13,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override ModType Type => ModType.System;
|
||||
|
||||
public override bool Ranked => true;
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double StaminaStrain;
|
||||
public double RhythmStrain;
|
||||
public double ColourStrain;
|
||||
public double ApproachRate;
|
||||
public double GreatHitWindow;
|
||||
public double StaminaStrain { get; set; }
|
||||
public double RhythmStrain { get; set; }
|
||||
public double ColourStrain { get; set; }
|
||||
public double ApproachRate { get; set; }
|
||||
public double GreatHitWindow { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
|
@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public class TaikoModHardRock : ModHardRock
|
||||
{
|
||||
public override double ScoreMultiplier => 1.06;
|
||||
public override bool Ranked => true;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier factor added to the scrolling speed.
|
||||
|
@ -3,14 +3,19 @@
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class Hit : TaikoStrongableHitObject
|
||||
public class Hit : TaikoStrongableHitObject, IHasDisplayColour
|
||||
{
|
||||
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
|
||||
|
||||
public Bindable<Color4> DisplayColour { get; } = new Bindable<Color4>(COLOUR_CENTRE);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
|
||||
/// </summary>
|
||||
@ -20,9 +25,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
set => TypeBindable.Value = value;
|
||||
}
|
||||
|
||||
public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177");
|
||||
public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb");
|
||||
|
||||
public Hit()
|
||||
{
|
||||
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
TypeBindable.BindValueChanged(_ =>
|
||||
{
|
||||
updateSamplesFromType();
|
||||
DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM;
|
||||
});
|
||||
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.PinkDarker;
|
||||
AccentColour = Hit.COLOUR_CENTRE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.BlueDarker;
|
||||
AccentColour = Hit.COLOUR_RIM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,10 +33,11 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
* outside of the range.
|
||||
*/
|
||||
|
||||
[Test]
|
||||
public void TestApplyStarQueries()
|
||||
[TestCase("star")]
|
||||
[TestCase("stars")]
|
||||
public void TestApplyStarQueries(string variant)
|
||||
{
|
||||
const string query = "stars<4 easy";
|
||||
string query = $"{variant}<4 easy";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
|
||||
|
@ -1,2 +1,2 @@
|
||||
[General]
|
||||
Version: 1.0
|
||||
// no version specified means v1
|
||||
|
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 B |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 B |
116
osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
Normal file
116
osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Skins
|
||||
{
|
||||
[TestFixture]
|
||||
[HeadlessTest]
|
||||
public class TestSceneBeatmapSkinLookupDisables : OsuTestScene
|
||||
{
|
||||
private UserSkinSource userSource;
|
||||
private BeatmapSkinSource beatmapSource;
|
||||
private SkinRequester requester;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Add(new SkinProvidingContainer(userSource = new UserSkinSource())
|
||||
.WithChild(new BeatmapSkinProvidingContainer(beatmapSource = new BeatmapSkinSource())
|
||||
.WithChild(requester = new SkinRequester())));
|
||||
});
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestDrawableLookup(bool allowBeatmapLookups)
|
||||
{
|
||||
AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups));
|
||||
|
||||
string expected = allowBeatmapLookups ? "beatmap" : "user";
|
||||
|
||||
AddAssert($"Check lookup is from {expected}", () => requester.GetDrawableComponent(new TestSkinComponent())?.Name == expected);
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestProviderLookup(bool allowBeatmapLookups)
|
||||
{
|
||||
AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups));
|
||||
|
||||
ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource;
|
||||
|
||||
AddAssert("Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected());
|
||||
}
|
||||
|
||||
public class UserSkinSource : LegacySkin
|
||||
{
|
||||
public UserSkinSource()
|
||||
: base(new SkinInfo(), null, null, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
return new Container { Name = "user" };
|
||||
}
|
||||
}
|
||||
|
||||
public class BeatmapSkinSource : LegacyBeatmapSkin
|
||||
{
|
||||
public BeatmapSkinSource()
|
||||
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
return new Container { Name = "beatmap" };
|
||||
}
|
||||
}
|
||||
|
||||
public class SkinRequester : Drawable, ISkin
|
||||
{
|
||||
private ISkinSource skin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
this.skin = skin;
|
||||
}
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
|
||||
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
|
||||
|
||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => skin.FindProvider(lookupFunction);
|
||||
}
|
||||
|
||||
private class TestSkinComponent : ISkinComponent
|
||||
{
|
||||
public string LookupName => string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
144
osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs
Normal file
144
osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneMetadataSection : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap());
|
||||
|
||||
private TestMetadataSection metadataSection;
|
||||
|
||||
[Test]
|
||||
public void TestMinimalMetadata()
|
||||
{
|
||||
AddStep("set metadata", () =>
|
||||
{
|
||||
editorBeatmap.Metadata.Artist = "Example Artist";
|
||||
editorBeatmap.Metadata.ArtistUnicode = null;
|
||||
|
||||
editorBeatmap.Metadata.Title = "Example Title";
|
||||
editorBeatmap.Metadata.TitleUnicode = null;
|
||||
});
|
||||
|
||||
createSection();
|
||||
|
||||
assertArtist("Example Artist");
|
||||
assertRomanisedArtist("Example Artist", false);
|
||||
|
||||
assertTitle("Example Title");
|
||||
assertRomanisedTitle("Example Title", false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialisationFromNonRomanisedVariant()
|
||||
{
|
||||
AddStep("set metadata", () =>
|
||||
{
|
||||
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||
editorBeatmap.Metadata.Artist = null;
|
||||
|
||||
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||
editorBeatmap.Metadata.Title = null;
|
||||
});
|
||||
|
||||
createSection();
|
||||
|
||||
assertArtist("*なみりん");
|
||||
assertRomanisedArtist(string.Empty, true);
|
||||
|
||||
assertTitle("コイシテイク・プラネット");
|
||||
assertRomanisedTitle(string.Empty, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialisationPreservesOriginalValues()
|
||||
{
|
||||
AddStep("set metadata", () =>
|
||||
{
|
||||
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||
editorBeatmap.Metadata.Artist = "*namirin";
|
||||
|
||||
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||
editorBeatmap.Metadata.Title = "Koishiteiku Planet";
|
||||
});
|
||||
|
||||
createSection();
|
||||
|
||||
assertArtist("*なみりん");
|
||||
assertRomanisedArtist("*namirin", true);
|
||||
|
||||
assertTitle("コイシテイク・プラネット");
|
||||
assertRomanisedTitle("Koishiteiku Planet", true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueTransfer()
|
||||
{
|
||||
AddStep("set metadata", () =>
|
||||
{
|
||||
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||
editorBeatmap.Metadata.Artist = null;
|
||||
|
||||
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||
editorBeatmap.Metadata.Title = null;
|
||||
});
|
||||
|
||||
createSection();
|
||||
|
||||
AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin");
|
||||
assertArtist("*namirin");
|
||||
assertRomanisedArtist("*namirin", false);
|
||||
|
||||
AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん");
|
||||
assertArtist("*なみりん");
|
||||
assertRomanisedArtist("*namirin", true);
|
||||
|
||||
AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori");
|
||||
assertTitle("Hitokoto no kyori");
|
||||
assertRomanisedTitle("Hitokoto no kyori", false);
|
||||
|
||||
AddStep("set native title", () => metadataSection.TitleTextBox.Current.Value = "ヒトコトの距離");
|
||||
assertTitle("ヒトコトの距離");
|
||||
assertRomanisedTitle("Hitokoto no kyori", true);
|
||||
}
|
||||
|
||||
private void createSection()
|
||||
=> AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection());
|
||||
|
||||
private void assertArtist(string expected)
|
||||
=> AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected);
|
||||
|
||||
private void assertRomanisedArtist(string expected, bool editable)
|
||||
{
|
||||
AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected);
|
||||
AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable);
|
||||
}
|
||||
|
||||
private void assertTitle(string expected)
|
||||
=> AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected);
|
||||
|
||||
private void assertRomanisedTitle(string expected, bool editable)
|
||||
{
|
||||
AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected);
|
||||
AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable);
|
||||
}
|
||||
|
||||
private class TestMetadataSection : MetadataSection
|
||||
{
|
||||
public new LabelledTextBox ArtistTextBox => base.ArtistTextBox;
|
||||
public new LabelledTextBox RomanisedArtistTextBox => base.RomanisedArtistTextBox;
|
||||
|
||||
public new LabelledTextBox TitleTextBox => base.TitleTextBox;
|
||||
public new LabelledTextBox RomanisedTitleTextBox => base.RomanisedTitleTextBox;
|
||||
}
|
||||
}
|
||||
}
|
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneMessageNotifier : OsuManualInputManagerTestScene
|
||||
{
|
||||
private User friend;
|
||||
private Channel publicChannel;
|
||||
private Channel privateMessageChannel;
|
||||
private TestContainer testContainer;
|
||||
|
||||
private int messageIdCounter;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
if (API is DummyAPIAccess daa)
|
||||
{
|
||||
daa.HandleRequest = dummyAPIHandleRequest;
|
||||
}
|
||||
|
||||
friend = new User { Id = 0, Username = "Friend" };
|
||||
publicChannel = new Channel { Id = 1, Name = "osu" };
|
||||
privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM };
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel })
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
testContainer.ChatOverlay.Show();
|
||||
});
|
||||
}
|
||||
|
||||
private bool dummyAPIHandleRequest(APIRequest request)
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetMessagesRequest messagesRequest:
|
||||
messagesRequest.TriggerSuccess(new List<Message>(0));
|
||||
return true;
|
||||
|
||||
case CreateChannelRequest createChannelRequest:
|
||||
var apiChatChannel = new APIChatChannel
|
||||
{
|
||||
RecentMessages = new List<Message>(0),
|
||||
ChannelID = (int)createChannelRequest.Channel.Id
|
||||
};
|
||||
createChannelRequest.TriggerSuccess(apiChatChannel);
|
||||
return true;
|
||||
|
||||
case ListChannelsRequest listChannelsRequest:
|
||||
listChannelsRequest.TriggerSuccess(new List<Channel>(1) { publicChannel });
|
||||
return true;
|
||||
|
||||
case GetUpdatesRequest updatesRequest:
|
||||
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||
{
|
||||
Messages = new List<Message>(0),
|
||||
Presence = new List<Channel>(0)
|
||||
});
|
||||
return true;
|
||||
|
||||
case JoinChannelRequest joinChannelRequest:
|
||||
joinChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPublicChannelMention()
|
||||
{
|
||||
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive public message", () => receiveMessage(friend, publicChannel, "Hello everyone"));
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
|
||||
AddStep("receive message containing mention", () => receiveMessage(friend, publicChannel, $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!"));
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddStep("click notification", clickNotification<MessageNotifier.MentionNotification>);
|
||||
|
||||
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("public channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrivateMessageNotification()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, $"Hello {API.LocalUser.Value.Username}"));
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddStep("click notification", clickNotification<MessageNotifier.PrivateMessageNotification>);
|
||||
|
||||
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("PM channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationWhenPMChannelOpen()
|
||||
{
|
||||
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, "you're reading this, right?"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationWhenMentionedInOpenPublicChannel()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive mention", () => receiveMessage(friend, publicChannel, $"{API.LocalUser.Value.Username.ToUpperInvariant()} has been reading this"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationOnSelfMention()
|
||||
{
|
||||
AddStep("switch to PM channel", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive self-mention", () => receiveMessage(API.LocalUser.Value, publicChannel, $"my name is {API.LocalUser.Value.Username}"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationOnPMFromSelf()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive PM from self", () => receiveMessage(API.LocalUser.Value, privateMessageChannel, "hey hey"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotificationsNotFiredTwice()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive same PM twice", () =>
|
||||
{
|
||||
var message = createMessage(friend, privateMessageChannel, "hey hey");
|
||||
privateMessageChannel.AddNewMessages(message, message);
|
||||
});
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
}
|
||||
|
||||
private void receiveMessage(User sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
||||
|
||||
private Message createMessage(User sender, Channel channel, string content) => new Message(messageIdCounter++)
|
||||
{
|
||||
Content = content,
|
||||
Sender = sender,
|
||||
ChannelId = channel.Id
|
||||
};
|
||||
|
||||
private void clickNotification<T>() where T : Notification
|
||||
{
|
||||
var notification = testContainer.NotificationOverlay.ChildrenOfType<T>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(notification);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
private class TestContainer : Container
|
||||
{
|
||||
[Cached]
|
||||
public ChannelManager ChannelManager { get; } = new ChannelManager();
|
||||
|
||||
[Cached]
|
||||
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
};
|
||||
|
||||
[Cached]
|
||||
public ChatOverlay ChatOverlay { get; } = new ChatOverlay();
|
||||
|
||||
private readonly MessageNotifier messageNotifier = new MessageNotifier();
|
||||
|
||||
private readonly Channel[] channels;
|
||||
|
||||
public TestContainer(Channel[] channels)
|
||||
{
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
ChannelManager,
|
||||
ChatOverlay,
|
||||
NotificationOverlay,
|
||||
messageNotifier,
|
||||
};
|
||||
|
||||
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
|
||||
|
||||
foreach (var channel in channels)
|
||||
ChannelManager.JoinChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -143,6 +143,25 @@ Line after image";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTableWithImageContent()
|
||||
{
|
||||
AddStep("Add Table", () =>
|
||||
{
|
||||
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||
markdownContainer.Text = @"
|
||||
| Image | Name | Effect |
|
||||
| :-: | :-: | :-- |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
";
|
||||
});
|
||||
}
|
||||
|
||||
private class TestMarkdownContainer : WikiMarkdownContainer
|
||||
{
|
||||
public LinkInline Link;
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
}
|
||||
}
|
||||
},
|
||||
new AccuracyCircle(score)
|
||||
new AccuracyCircle(score, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -2,11 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@ -21,6 +24,45 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[TestCase(true)]
|
||||
public void TestNonPadded(bool hasDescription) => createPaddedComponent(hasDescription, false);
|
||||
|
||||
[Test]
|
||||
public void TestFixedWidth()
|
||||
{
|
||||
const float label_width = 200;
|
||||
|
||||
AddStep("create components", () => Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new NonPaddedLabelledDrawable
|
||||
{
|
||||
Label = "short",
|
||||
FixedLabelWidth = label_width
|
||||
},
|
||||
new NonPaddedLabelledDrawable
|
||||
{
|
||||
Label = "very very very very very very very very very very very long",
|
||||
FixedLabelWidth = label_width
|
||||
},
|
||||
new PaddedLabelledDrawable
|
||||
{
|
||||
Label = "short",
|
||||
FixedLabelWidth = label_width
|
||||
},
|
||||
new PaddedLabelledDrawable
|
||||
{
|
||||
Label = "very very very very very very very very very very very long",
|
||||
FixedLabelWidth = label_width
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("unset label width", () => this.ChildrenOfType<LabelledDrawable<Drawable>>().ForEach(d => d.FixedLabelWidth = null));
|
||||
AddStep("reset label width", () => this.ChildrenOfType<LabelledDrawable<Drawable>>().ForEach(d => d.FixedLabelWidth = label_width));
|
||||
}
|
||||
|
||||
private void createPaddedComponent(bool hasDescription = false, bool padded = true)
|
||||
{
|
||||
AddStep("create component", () =>
|
||||
|
47
osu.Game/Beatmaps/MetadataUtils.cs
Normal file
47
osu.Game/Beatmaps/MetadataUtils.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Groups utility methods used to handle beatmap metadata.
|
||||
/// </summary>
|
||||
public static class MetadataUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the character <paramref name="c"/> can be used in <see cref="BeatmapMetadata.Artist"/> and <see cref="BeatmapMetadata.Title"/> fields.
|
||||
/// Characters not matched by this method can be placed in <see cref="BeatmapMetadata.ArtistUnicode"/> and <see cref="BeatmapMetadata.TitleUnicode"/>.
|
||||
/// </summary>
|
||||
public static bool IsRomanised(char c) => c <= 0xFF;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the string <paramref name="str"/> can be used in <see cref="BeatmapMetadata.Artist"/> and <see cref="BeatmapMetadata.Title"/> fields.
|
||||
/// Strings not matched by this method can be placed in <see cref="BeatmapMetadata.ArtistUnicode"/> and <see cref="BeatmapMetadata.TitleUnicode"/>.
|
||||
/// </summary>
|
||||
public static bool IsRomanised(string? str) => string.IsNullOrEmpty(str) || str.All(IsRomanised);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of <paramref name="str"/> with all characters that do not match <see cref="IsRomanised(char)"/> removed.
|
||||
/// </summary>
|
||||
public static string StripNonRomanisedCharacters(string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
var stringBuilder = new StringBuilder(str.Length);
|
||||
|
||||
foreach (var c in str)
|
||||
{
|
||||
if (IsRomanised(c))
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
return stringBuilder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
@ -61,6 +61,9 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
|
||||
|
||||
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
|
||||
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
|
||||
|
||||
// Audio
|
||||
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
||||
|
||||
@ -259,6 +262,8 @@ namespace osu.Game.Configuration
|
||||
ScalingSizeY,
|
||||
UIScale,
|
||||
IntroSequence,
|
||||
NotifyOnUsernameMentioned,
|
||||
NotifyOnPrivateMessage,
|
||||
UIHoldActivationDelay,
|
||||
HitLighting,
|
||||
MenuBackgroundSource,
|
||||
|
@ -1,20 +0,0 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
public enum RankingType
|
||||
{
|
||||
Local,
|
||||
|
||||
[Description("Global")]
|
||||
Top,
|
||||
|
||||
[Description("Selected Mods")]
|
||||
SelectedMod,
|
||||
Friends,
|
||||
Country
|
||||
}
|
||||
}
|
20
osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
Normal file
20
osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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 Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownImage : MarkdownImage, IHasTooltip
|
||||
{
|
||||
public string TooltipText { get; }
|
||||
|
||||
public OsuMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline.Url)
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
||||
|
||||
protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline));
|
||||
|
||||
// TODO : Change font to monospace
|
||||
protected override void AddCodeInLine(CodeInline codeInline) => AddDrawable(new OsuMarkdownInlineCode
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet);
|
||||
|
||||
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
||||
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||
{
|
||||
this.sampleSet = sampleSet;
|
||||
}
|
||||
|
@ -107,10 +107,10 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
}
|
||||
|
||||
private bool playedPopInSound;
|
||||
|
||||
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
||||
{
|
||||
bool didChange = state.NewValue != state.OldValue;
|
||||
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case Visibility.Visible:
|
||||
@ -121,18 +121,15 @@ namespace osu.Game.Graphics.Containers
|
||||
return;
|
||||
}
|
||||
|
||||
samplePopIn?.Play();
|
||||
playedPopInSound = true;
|
||||
if (didChange)
|
||||
samplePopIn?.Play();
|
||||
|
||||
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
||||
break;
|
||||
|
||||
case Visibility.Hidden:
|
||||
if (playedPopInSound)
|
||||
{
|
||||
if (didChange)
|
||||
samplePopOut?.Play();
|
||||
playedPopInSound = false;
|
||||
}
|
||||
|
||||
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
||||
break;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -23,9 +22,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private const int text_size = 17;
|
||||
private const int transition_length = 80;
|
||||
|
||||
private Sample sampleClick;
|
||||
private Sample sampleHover;
|
||||
|
||||
private TextContainer text;
|
||||
|
||||
public DrawableOsuMenuItem(MenuItem item)
|
||||
@ -36,12 +32,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleHover = audio.Samples.Get(@"UI/generic-hover");
|
||||
sampleClick = audio.Samples.Get(@"UI/generic-select");
|
||||
|
||||
BackgroundColour = Color4.Transparent;
|
||||
BackgroundColourHover = Color4Extensions.FromHex(@"172023");
|
||||
|
||||
AddInternal(new HoverClickSounds());
|
||||
|
||||
updateTextColour();
|
||||
|
||||
Item.Action.BindDisabledChanged(_ => updateState(), true);
|
||||
@ -84,7 +79,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
if (IsHovered && !Item.Action.Disabled)
|
||||
{
|
||||
sampleHover.Play();
|
||||
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
||||
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
||||
}
|
||||
@ -95,12 +89,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
sampleClick.Play();
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
protected sealed override Drawable CreateContent() => text = CreateTextContainer();
|
||||
protected virtual TextContainer CreateTextContainer() => new TextContainer();
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// Array of button codes which should trigger the click sound.
|
||||
/// If this optional parameter is omitted or set to <code>null</code>, the click sound will only be played on left click.
|
||||
/// </param>
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null)
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[] buttons = null)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.buttons = buttons ?? new[] { MouseButton.Left };
|
||||
@ -45,7 +45,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}");
|
||||
sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select")
|
||||
?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
osu.Game/Graphics/UserInterface/HoverSampleSet.cs
Normal file
25
osu.Game/Graphics/UserInterface/HoverSampleSet.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 System.ComponentModel;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public enum HoverSampleSet
|
||||
{
|
||||
[Description("default")]
|
||||
Default,
|
||||
|
||||
[Description("button")]
|
||||
Button,
|
||||
|
||||
[Description("softer")]
|
||||
Soft,
|
||||
|
||||
[Description("toolbar")]
|
||||
Toolbar,
|
||||
|
||||
[Description("songselect")]
|
||||
SongSelect
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// 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.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -22,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected readonly HoverSampleSet SampleSet;
|
||||
|
||||
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
||||
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||
{
|
||||
SampleSet = sampleSet;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -31,7 +30,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, SessionStatics statics)
|
||||
{
|
||||
sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
||||
sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover")
|
||||
?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover");
|
||||
}
|
||||
|
||||
public override void PlayHoverSample()
|
||||
@ -40,22 +40,4 @@ namespace osu.Game.Graphics.UserInterface
|
||||
sampleHover.Play();
|
||||
}
|
||||
}
|
||||
|
||||
public enum HoverSampleSet
|
||||
{
|
||||
[Description("")]
|
||||
Loud,
|
||||
|
||||
[Description("-soft")]
|
||||
Normal,
|
||||
|
||||
[Description("-softer")]
|
||||
Soft,
|
||||
|
||||
[Description("-toolbar")]
|
||||
Toolbar,
|
||||
|
||||
[Description("-songselect")]
|
||||
SongSelect
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private readonly Box hover;
|
||||
|
||||
public OsuAnimatedButton()
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
base.Content.Add(content = new Container
|
||||
{
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected Box Background;
|
||||
protected SpriteText SpriteText;
|
||||
|
||||
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Loud)
|
||||
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
|
||||
{
|
||||
Height = 40;
|
||||
|
||||
|
@ -14,6 +14,27 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
public abstract class LabelledDrawable<T> : CompositeDrawable
|
||||
where T : Drawable
|
||||
{
|
||||
private float? fixedLabelWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The fixed width of the label of this <see cref="LabelledDrawable{T}"/>.
|
||||
/// If <c>null</c>, the label portion will auto-size to its content.
|
||||
/// Can be used in layout scenarios where several labels must match in length for the components to be aligned properly.
|
||||
/// </summary>
|
||||
public float? FixedLabelWidth
|
||||
{
|
||||
get => fixedLabelWidth;
|
||||
set
|
||||
{
|
||||
if (fixedLabelWidth == value)
|
||||
return;
|
||||
|
||||
fixedLabelWidth = value;
|
||||
|
||||
updateLabelWidth();
|
||||
}
|
||||
}
|
||||
|
||||
protected const float CONTENT_PADDING_VERTICAL = 10;
|
||||
protected const float CONTENT_PADDING_HORIZONTAL = 15;
|
||||
protected const float CORNER_RADIUS = 15;
|
||||
@ -23,6 +44,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
protected readonly T Component;
|
||||
|
||||
private readonly GridContainer grid;
|
||||
private readonly OsuTextFlowContainer labelText;
|
||||
private readonly OsuTextFlowContainer descriptionText;
|
||||
|
||||
@ -56,7 +78,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Spacing = new Vector2(0, 12),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
grid = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -69,7 +91,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 20 }
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Right = 20,
|
||||
// ensure that the label is always vertically padded even if the component itself isn't.
|
||||
// this may become an issue if the label is taller than the component.
|
||||
Vertical = padded ? 0 : CONTENT_PADDING_VERTICAL
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -87,7 +115,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
},
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||
},
|
||||
descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
|
||||
{
|
||||
@ -99,6 +126,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateLabelWidth();
|
||||
}
|
||||
|
||||
private void updateLabelWidth()
|
||||
{
|
||||
if (fixedLabelWidth == null)
|
||||
{
|
||||
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) };
|
||||
labelText.RelativeSizeAxes = Axes.None;
|
||||
labelText.AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
else
|
||||
{
|
||||
grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, fixedLabelWidth.Value) };
|
||||
labelText.AutoSizeAxes = Axes.Y;
|
||||
labelText.RelativeSizeAxes = Axes.X;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public bool ReadOnly
|
||||
{
|
||||
get => Component.ReadOnly;
|
||||
set => Component.ReadOnly = value;
|
||||
}
|
||||
|
||||
@ -45,14 +46,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Component.BorderColour = colours.Blue;
|
||||
}
|
||||
|
||||
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox
|
||||
{
|
||||
CommitOnFocusLost = true,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
};
|
||||
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox();
|
||||
|
||||
public override bool AcceptsFocus => true;
|
||||
|
||||
@ -64,6 +58,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
||||
{
|
||||
t.CommitOnFocusLost = true;
|
||||
t.Anchor = Anchor.Centre;
|
||||
t.Origin = Anchor.Centre;
|
||||
t.RelativeSizeAxes = Axes.X;
|
||||
t.CornerRadius = CORNER_RADIUS;
|
||||
|
||||
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
|
||||
});
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class CreateChannelRequest : APIRequest<APIChatChannel>
|
||||
{
|
||||
private readonly Channel channel;
|
||||
public readonly Channel Channel;
|
||||
|
||||
public CreateChannelRequest(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests
|
||||
req.Method = HttpMethod.Post;
|
||||
|
||||
req.AddParameter("type", $"{ChannelType.PM}");
|
||||
req.AddParameter("target_id", $"{channel.Users.First().Id}");
|
||||
req.AddParameter("target_id", $"{Channel.Users.First().Id}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
@ -63,5 +63,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}";
|
||||
}
|
||||
}
|
||||
|
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
@ -0,0 +1,181 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that handles creating and posting notifications for incoming messages.
|
||||
/// </summary>
|
||||
public class MessageNotifier : Component
|
||||
{
|
||||
[Resolved]
|
||||
private NotificationOverlay notifications { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ChatOverlay chatOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ChannelManager channelManager { get; set; }
|
||||
|
||||
private Bindable<bool> notifyOnUsername;
|
||||
private Bindable<bool> notifyOnPrivateMessage;
|
||||
|
||||
private readonly IBindable<User> localUser = new Bindable<User>();
|
||||
private readonly IBindableList<Channel> joinedChannels = new BindableList<Channel>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IAPIProvider api)
|
||||
{
|
||||
notifyOnUsername = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned);
|
||||
notifyOnPrivateMessage = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage);
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
joinedChannels.BindTo(channelManager.JoinedChannels);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
joinedChannels.BindCollectionChanged(channelsChanged, true);
|
||||
}
|
||||
|
||||
private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var channel in e.NewItems.Cast<Channel>())
|
||||
channel.NewMessagesArrived += checkNewMessages;
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var channel in e.OldItems.Cast<Channel>())
|
||||
channel.NewMessagesArrived -= checkNewMessages;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNewMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
if (!messages.Any())
|
||||
return;
|
||||
|
||||
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
|
||||
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
// Only send notifications, if ChatOverlay and the target channel aren't visible.
|
||||
if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel)
|
||||
return;
|
||||
|
||||
foreach (var message in messages.OrderByDescending(m => m.Id))
|
||||
{
|
||||
// ignore messages that already have been read
|
||||
if (message.Id <= channel.LastReadId)
|
||||
return;
|
||||
|
||||
if (message.Sender.Id == localUser.Value.Id)
|
||||
continue;
|
||||
|
||||
// check for private messages first to avoid both posting two notifications about the same message
|
||||
if (checkForPMs(channel, message))
|
||||
continue;
|
||||
|
||||
checkForMentions(channel, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the user enabled private message notifications and whether specified <paramref name="message"/> is a direct message.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel associated to the <paramref name="message"/></param>
|
||||
/// <param name="message">The message to be checked</param>
|
||||
/// <returns>Whether a notification was fired.</returns>
|
||||
private bool checkForPMs(Channel channel, Message message)
|
||||
{
|
||||
if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM)
|
||||
return false;
|
||||
|
||||
notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkForMentions(Channel channel, Message message)
|
||||
{
|
||||
if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return;
|
||||
|
||||
notifications.Post(new MentionNotification(message.Sender.Username, channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if <paramref name="message"/> contains <paramref name="username"/>.
|
||||
/// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces).
|
||||
/// </summary>
|
||||
private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public class PrivateMessageNotification : OpenChannelNotification
|
||||
{
|
||||
public PrivateMessageNotification(string username, Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Envelope;
|
||||
Text = $"You received a private message from '{username}'. Click to read it!";
|
||||
}
|
||||
}
|
||||
|
||||
public class MentionNotification : OpenChannelNotification
|
||||
{
|
||||
public MentionNotification(string username, Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
Icon = FontAwesome.Solid.At;
|
||||
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class OpenChannelNotification : SimpleNotification
|
||||
{
|
||||
protected OpenChannelNotification(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
private readonly Channel channel;
|
||||
|
||||
public override bool IsImportant => false;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager)
|
||||
{
|
||||
IconBackgound.Colour = colours.PurpleDark;
|
||||
|
||||
Activated = delegate
|
||||
{
|
||||
notificationOverlay.Hide();
|
||||
chatOverlay.Show();
|
||||
channelManager.CurrentChannel.Value = channel;
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -728,6 +728,7 @@ namespace osu.Game
|
||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
||||
loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
public Info()
|
||||
{
|
||||
MetadataSection source, tags, genre, language;
|
||||
OsuSpriteText unrankedPlaceholder;
|
||||
OsuSpriteText notRankedPlaceholder;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = base_height;
|
||||
@ -102,12 +102,12 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 20, Horizontal = 15 },
|
||||
},
|
||||
unrankedPlaceholder = new OsuSpriteText
|
||||
notRankedPlaceholder = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Text = "Unranked beatmap",
|
||||
Text = "This beatmap is not ranked",
|
||||
Font = OsuFont.GetFont(size: 12)
|
||||
},
|
||||
},
|
||||
@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty;
|
||||
var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0;
|
||||
successRate.Alpha = setHasLeaderboard ? 1 : 0;
|
||||
unrankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
|
||||
notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
|
||||
Height = setHasLeaderboard ? 270 : base_height;
|
||||
};
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
return;
|
||||
|
||||
modsContainer.Add(new ModButton(new ModNoMod()));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m)));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m)));
|
||||
|
||||
modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Input.Bindings;
|
||||
@ -25,8 +24,6 @@ namespace osu.Game.Overlays
|
||||
|
||||
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
|
||||
|
||||
private Sample sampleBack;
|
||||
|
||||
private List<APIChangelogBuild> builds;
|
||||
|
||||
protected List<APIUpdateStream> Streams;
|
||||
@ -41,8 +38,6 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
Header.Build.BindTarget = Current;
|
||||
|
||||
sampleBack = audio.Samples.Get(@"UI/generic-select-soft");
|
||||
|
||||
Current.BindValueChanged(e =>
|
||||
{
|
||||
if (e.NewValue != null)
|
||||
@ -108,7 +103,6 @@ namespace osu.Game.Overlays
|
||||
else
|
||||
{
|
||||
Current.Value = null;
|
||||
sampleBack?.Play();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -6,18 +6,18 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
|
@ -0,0 +1,32 @@
|
||||
// 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.Configuration;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Online
|
||||
{
|
||||
public class AlertsAndPrivacySettings : SettingsSubsection
|
||||
{
|
||||
protected override string Header => "Alerts and Privacy";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show a notification when someone mentions your name",
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show a notification when you receive a private message",
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new WebSettings(),
|
||||
new AlertsAndPrivacySettings(),
|
||||
new IntegrationSettings()
|
||||
};
|
||||
}
|
||||
|
@ -2,19 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
public class WikiMarkdownImage : MarkdownImage, IHasTooltip
|
||||
public class WikiMarkdownImage : OsuMarkdownImage
|
||||
{
|
||||
public string TooltipText { get; }
|
||||
|
||||
public WikiMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline.Url)
|
||||
: base(linkInline)
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
|
||||
protected override ImageContainer CreateImageContainer(string url)
|
||||
|
@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public class DifficultyAttributes
|
||||
{
|
||||
public Mod[] Mods;
|
||||
public Skill[] Skills;
|
||||
public Mod[] Mods { get; set; }
|
||||
public Skill[] Skills { get; set; }
|
||||
|
||||
public double StarRating;
|
||||
public int MaxCombo;
|
||||
public double StarRating { get; set; }
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
public DifficultyAttributes()
|
||||
{
|
||||
|
@ -108,10 +108,14 @@ namespace osu.Game.Rulesets.Mods
|
||||
public virtual bool HasImplementation => this is IApplicableMod;
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this mod is ranked.
|
||||
/// Whether this mod is playable by an end user.
|
||||
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual bool Ranked => false;
|
||||
public virtual bool UserPlayable => true;
|
||||
|
||||
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009
|
||||
public virtual bool IsRanked => false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod requires configuration to apply changes to the game.
|
||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
public override bool UserPlayable => false;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
||||
|
||||
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
|
||||
|
@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override string Description => "Feeling nostalgic?";
|
||||
|
||||
public override bool Ranked => false;
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModDoubletime;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override string Description => "Zoooooooooom...";
|
||||
public override bool Ranked => true;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray();
|
||||
|
||||
|
@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModEasy;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override bool Ranked => true;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) };
|
||||
|
||||
public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
||||
|
@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModFlashlight;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override string Description => "Restricted view area.";
|
||||
public override bool Ranked => true;
|
||||
|
||||
internal ModFlashlight()
|
||||
{
|
||||
|
@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModHalftime;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override string Description => "Less zoom...";
|
||||
public override bool Ranked => true;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray();
|
||||
|
||||
|
@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Acronym => "HD";
|
||||
public override IconUsage? Icon => OsuIcon.ModHidden;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
|
@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override string Description => "You can't fail, no matter what.";
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override bool Ranked => true;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) };
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Acronym => "PF";
|
||||
public override IconUsage? Icon => OsuIcon.ModPerfect;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override bool Ranked => true;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override string Description => "SS or quit.";
|
||||
|
||||
|
@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override string Description => "Miss and fail.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray();
|
||||
|
||||
|
@ -716,7 +716,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
if (HitObject != null)
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
|
||||
CurrentSkin.SourceChanged -= skinSourceChanged;
|
||||
if (CurrentSkin != null)
|
||||
CurrentSkin.SourceChanged -= skinSourceChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal file
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject which has a preferred display colour. Will be used for editor timeline display.
|
||||
/// </summary>
|
||||
public interface IHasDisplayColour
|
||||
{
|
||||
/// <summary>
|
||||
/// The current display colour of this hit object.
|
||||
/// </summary>
|
||||
Bindable<Color4> DisplayColour { get; }
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private Bindable<int> indexInCurrentComboBindable;
|
||||
private Bindable<int> comboIndexBindable;
|
||||
private Bindable<Color4> displayColourBindable;
|
||||
|
||||
private readonly ExtendableCircle circle;
|
||||
private readonly Border border;
|
||||
@ -108,44 +109,64 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (Item is IHasComboInformation comboInfo)
|
||||
switch (Item)
|
||||
{
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
case IHasDisplayColour displayColour:
|
||||
displayColourBindable = displayColour.DisplayColour.GetBoundCopy();
|
||||
displayColourBindable.BindValueChanged(_ => updateColour(), true);
|
||||
break;
|
||||
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
case IHasComboInformation comboInfo:
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
|
||||
skin.SourceChanged += updateComboColour;
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateColour(), true);
|
||||
|
||||
skin.SourceChanged += updateColour;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSelected()
|
||||
{
|
||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||
updateComboColour();
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void OnDeselected()
|
||||
{
|
||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||
updateComboColour();
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||
|
||||
private void updateComboColour()
|
||||
private void updateColour()
|
||||
{
|
||||
if (!(Item is IHasComboInformation combo))
|
||||
return;
|
||||
Color4 colour;
|
||||
|
||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||
var comboColour = combo.GetComboColour(comboColours);
|
||||
switch (Item)
|
||||
{
|
||||
case IHasDisplayColour displayColour:
|
||||
colour = displayColour.DisplayColour.Value;
|
||||
break;
|
||||
|
||||
case IHasComboInformation combo:
|
||||
{
|
||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||
colour = combo.GetComboColour(comboColours);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSelected)
|
||||
{
|
||||
border.Show();
|
||||
comboColour = comboColour.Lighten(0.3f);
|
||||
colour = colour.Lighten(0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -153,9 +174,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
|
||||
if (Item is IHasDuration duration && duration.Duration > 0)
|
||||
circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f));
|
||||
circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f));
|
||||
else
|
||||
circle.Colour = comboColour;
|
||||
circle.Colour = colour;
|
||||
|
||||
var col = circle.Colour.TopLeft.Linear;
|
||||
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
comboColours = new LabelledColourPalette
|
||||
{
|
||||
Label = "Hitcircle / Slider Combos",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
ColourNamePrefix = "Combo"
|
||||
}
|
||||
};
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
circleSizeSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Object Size",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "The size of all hit objects",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)
|
||||
{
|
||||
@ -40,6 +41,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
healthDrainSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Health Drain",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "The rate of passive health drain throughout playable time",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate)
|
||||
{
|
||||
@ -52,6 +54,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
approachRateSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Approach Rate",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "The speed at which objects are presented to the player",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate)
|
||||
{
|
||||
@ -64,6 +67,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
overallDifficultySlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Overall Difficulty",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty)
|
||||
{
|
||||
|
20
osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs
Normal file
20
osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
internal class LabelledRomanisedTextBox : LabelledTextBox
|
||||
{
|
||||
protected override OsuTextBox CreateTextBox() => new RomanisedTextBox();
|
||||
|
||||
private class RomanisedTextBox : OsuTextBox
|
||||
{
|
||||
protected override bool CanAddCharacter(char character)
|
||||
=> MetadataUtils.IsRomanised(character);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,75 +3,117 @@
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
internal class MetadataSection : SetupSection
|
||||
{
|
||||
private LabelledTextBox artistTextBox;
|
||||
private LabelledTextBox titleTextBox;
|
||||
protected LabelledTextBox ArtistTextBox;
|
||||
protected LabelledTextBox RomanisedArtistTextBox;
|
||||
|
||||
protected LabelledTextBox TitleTextBox;
|
||||
protected LabelledTextBox RomanisedTitleTextBox;
|
||||
|
||||
private LabelledTextBox creatorTextBox;
|
||||
private LabelledTextBox difficultyTextBox;
|
||||
private LabelledTextBox sourceTextBox;
|
||||
private LabelledTextBox tagsTextBox;
|
||||
|
||||
public override LocalisableString Title => "Metadata";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
var metadata = Beatmap.Metadata;
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
artistTextBox = new LabelledTextBox
|
||||
{
|
||||
Label = "Artist",
|
||||
Current = { Value = Beatmap.Metadata.Artist },
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
titleTextBox = new LabelledTextBox
|
||||
{
|
||||
Label = "Title",
|
||||
Current = { Value = Beatmap.Metadata.Title },
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
creatorTextBox = new LabelledTextBox
|
||||
{
|
||||
Label = "Creator",
|
||||
Current = { Value = Beatmap.Metadata.AuthorString },
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
difficultyTextBox = new LabelledTextBox
|
||||
{
|
||||
Label = "Difficulty Name",
|
||||
Current = { Value = Beatmap.BeatmapInfo.Version },
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
ArtistTextBox = createTextBox<LabelledTextBox>("Artist",
|
||||
!string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist),
|
||||
RomanisedArtistTextBox = createTextBox<LabelledRomanisedTextBox>("Romanised Artist",
|
||||
!string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)),
|
||||
|
||||
Empty(),
|
||||
|
||||
TitleTextBox = createTextBox<LabelledTextBox>("Title",
|
||||
!string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title),
|
||||
RomanisedTitleTextBox = createTextBox<LabelledRomanisedTextBox>("Romanised Title",
|
||||
!string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)),
|
||||
|
||||
Empty(),
|
||||
|
||||
creatorTextBox = createTextBox<LabelledTextBox>("Creator", metadata.AuthorString),
|
||||
difficultyTextBox = createTextBox<LabelledTextBox>("Difficulty Name", Beatmap.BeatmapInfo.Version),
|
||||
sourceTextBox = createTextBox<LabelledTextBox>("Source", metadata.Source),
|
||||
tagsTextBox = createTextBox<LabelledTextBox>("Tags", metadata.Tags)
|
||||
};
|
||||
|
||||
foreach (var item in Children.OfType<LabelledTextBox>())
|
||||
item.OnCommit += onCommit;
|
||||
}
|
||||
|
||||
private TTextBox createTextBox<TTextBox>(string label, string initialValue)
|
||||
where TTextBox : LabelledTextBox, new()
|
||||
=> new TTextBox
|
||||
{
|
||||
Label = label,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Current = { Value = initialValue },
|
||||
TabbableContentContainer = this
|
||||
};
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (string.IsNullOrEmpty(artistTextBox.Current.Value))
|
||||
GetContainingInputManager().ChangeFocus(artistTextBox);
|
||||
if (string.IsNullOrEmpty(ArtistTextBox.Current.Value))
|
||||
GetContainingInputManager().ChangeFocus(ArtistTextBox);
|
||||
|
||||
ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox));
|
||||
TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox));
|
||||
updateReadOnlyState();
|
||||
}
|
||||
|
||||
private void transferIfRomanised(string value, LabelledTextBox target)
|
||||
{
|
||||
if (MetadataUtils.IsRomanised(value))
|
||||
target.Current.Value = value;
|
||||
|
||||
updateReadOnlyState();
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
private void updateReadOnlyState()
|
||||
{
|
||||
RomanisedArtistTextBox.ReadOnly = MetadataUtils.IsRomanised(ArtistTextBox.Current.Value);
|
||||
RomanisedTitleTextBox.ReadOnly = MetadataUtils.IsRomanised(TitleTextBox.Current.Value);
|
||||
}
|
||||
|
||||
private void onCommit(TextBox sender, bool newText)
|
||||
{
|
||||
if (!newText) return;
|
||||
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// for now, update on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Metadata.Artist = artistTextBox.Current.Value;
|
||||
Beatmap.Metadata.Title = titleTextBox.Current.Value;
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
private void updateMetadata()
|
||||
{
|
||||
Beatmap.Metadata.ArtistUnicode = ArtistTextBox.Current.Value;
|
||||
Beatmap.Metadata.Artist = RomanisedArtistTextBox.Current.Value;
|
||||
|
||||
Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value;
|
||||
Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value;
|
||||
|
||||
Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
|
||||
Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
||||
Beatmap.Metadata.Source = sourceTextBox.Current.Value;
|
||||
Beatmap.Metadata.Tags = tagsTextBox.Current.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png")
|
||||
{
|
||||
Label = "Background",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
PlaceholderText = "Click to select a background image",
|
||||
Current = { Value = working.Value.Metadata.BackgroundFile },
|
||||
Target = backgroundFileChooserContainer,
|
||||
@ -72,6 +73,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg")
|
||||
{
|
||||
Label = "Audio Track",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
PlaceholderText = "Click to select a track",
|
||||
Current = { Value = working.Value.Metadata.AudioFile },
|
||||
Target = audioTrackFileChooserContainer,
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
@ -15,6 +16,11 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
private readonly FillFlowContainer flow;
|
||||
|
||||
/// <summary>
|
||||
/// Used to align some of the child <see cref="LabelledDrawable{T}"/>s together to achieve a grid-like look.
|
||||
/// </summary>
|
||||
protected const float LABEL_WIDTH = 160;
|
||||
|
||||
[Resolved]
|
||||
protected OsuColour Colours { get; private set; }
|
||||
|
||||
@ -53,7 +59,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(10),
|
||||
Direction = FillDirection.Vertical,
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
|
||||
public const float TIMING_COLUMN_WIDTH = 220;
|
||||
public const float TIMING_COLUMN_WIDTH = 230;
|
||||
|
||||
public IEnumerable<ControlPointGroup> ControlGroups
|
||||
{
|
||||
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
Text = group.Time.ToEditorFormattedString(),
|
||||
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||
Width = 60,
|
||||
Width = 70,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
|
@ -202,7 +202,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Child = modDisplay = new ModDisplay
|
||||
{
|
||||
Scale = new Vector2(0.4f),
|
||||
DisplayUnrankedText = false,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user