1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 00:47:24 +08:00

Merge branch 'master' into android

This commit is contained in:
Albert Tang 2019-03-08 10:39:24 -06:00 committed by GitHub
commit 7f30fce3fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 697 additions and 395 deletions

View File

@ -10,6 +10,7 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Catch
new MultiMod(new CatchModAutoplay(), new ModCinema()), new MultiMod(new CatchModAutoplay(), new ModCinema()),
new CatchModRelax(), new CatchModRelax(),
}; };
case ModType.Fun:
return new Mod[]
{
new MultiMod(new ModWindUp<CatchHitObject>(), new ModWindDown<CatchHitObject>())
};
default: default:
return new Mod[] { }; return new Mod[] { };
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModAutoplay : ModAutoplay<CatchHitObject> public class CatchModAutoplay : ModAutoplay<CatchHitObject>
{ {
protected override Score CreateReplayScore(Beatmap<CatchHitObject> beatmap) => new Score public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{ {
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
Replay = new CatchAutoGenerator(beatmap).Generate(), Replay = new CatchAutoGenerator(beatmap).Generate(),

View File

@ -6,17 +6,20 @@ using System.Linq;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
internal class CatchAutoGenerator : AutoGenerator<CatchHitObject> internal class CatchAutoGenerator : AutoGenerator
{ {
public const double RELEASE_DELAY = 20; public const double RELEASE_DELAY = 20;
public CatchAutoGenerator(Beatmap<CatchHitObject> beatmap) public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
public CatchAutoGenerator(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
Replay = new Replay(); Replay = new Replay();

View File

@ -12,6 +12,7 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
@ -145,6 +146,11 @@ namespace osu.Game.Rulesets.Mania
{ {
new MultiMod(new ManiaModAutoplay(), new ModCinema()), new MultiMod(new ManiaModAutoplay(), new ModCinema()),
}; };
case ModType.Fun:
return new Mod[]
{
new MultiMod(new ModWindUp<ManiaHitObject>(), new ModWindDown<ManiaHitObject>())
};
default: default:
return new Mod[] { }; return new Mod[] { };
} }

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject> public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
{ {
protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) => new Score public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{ {
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),

View File

@ -5,13 +5,12 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays namespace osu.Game.Rulesets.Mania.Replays
{ {
internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject> internal class ManiaAutoGenerator : AutoGenerator
{ {
public const double RELEASE_DELAY = 20; public const double RELEASE_DELAY = 20;

View File

@ -16,18 +16,18 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
{ {
private GameplayCursor cursor; private GameplayCursorContainer cursorContainer;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) }; public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
public CursorContainer Cursor => cursor; public CursorContainer Cursor => cursorContainer;
public bool ProvidingUserCursor => true; public bool ProvidingUserCursor => true;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both });
} }
} }
} }

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
path = new SmoothPath path = new SmoothPath
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
PathWidth = 1 PathRadius = 1
}, },
marker = new CircularContainer marker = new CircularContainer
{ {

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
InternalChild = body = new ManualSliderBody InternalChild = body = new ManualSliderBody
{ {
AccentColour = Color4.Transparent, AccentColour = Color4.Transparent,
PathWidth = slider.Scale * 64 PathRadius = slider.Scale * 64
}; };
} }
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
body.BorderColour = colours.Yellow; body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true); PositionBindable.BindValueChanged(_ => updatePosition(), true);
ScaleBindable.BindValueChanged(scale => body.PathWidth = scale.NewValue * 64, true); ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true);
} }
private void updatePosition() => Position = slider.StackedPosition; private void updatePosition() => Position = slider.StackedPosition;

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -16,8 +15,14 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
} }
protected override CursorContainer CreateCursor() => null; protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor { Size = Vector2.One };
protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One }; private class OsuPlayfieldNoCursor : OsuPlayfield
{
public OsuPlayfieldNoCursor()
{
Cursor?.Expire();
}
}
} }
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{ {
ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
Replay = new OsuAutoGenerator(beatmap).Generate() Replay = new OsuAutoGenerator(beatmap).Generate()

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
Body = new SnakingSliderBody(s) Body = new SnakingSliderBody(s)
{ {
PathWidth = s.Scale * 64, PathRadius = s.Scale * 64,
}, },
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both }, ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale => scaleBindable.BindValueChanged(scale =>
{ {
Body.PathWidth = scale.NewValue * 64; Body.PathRadius = scale.NewValue * 64;
Ball.Scale = new Vector2(scale.NewValue); Ball.Scale = new Vector2(scale.NewValue);
}); });

View File

@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private readonly BufferedContainer container; private readonly BufferedContainer container;
public float PathWidth public float PathRadius
{ {
get => path.PathWidth; get => path.PathRadius;
set => path.PathWidth = value; set => path.PathRadius = value;
} }
/// <summary> /// <summary>

View File

@ -13,6 +13,7 @@ using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
@ -128,7 +129,8 @@ namespace osu.Game.Rulesets.Osu
{ {
new OsuModTransform(), new OsuModTransform(),
new OsuModWiggle(), new OsuModWiggle(),
new OsuModGrow() new OsuModGrow(),
new MultiMod(new ModWindUp<OsuHitObject>(), new ModWindDown<OsuHitObject>()),
}; };
default: default:
return new Mod[] { }; return new Mod[] { };

View File

@ -10,12 +10,15 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
public class OsuAutoGenerator : OsuAutoGeneratorBase public class OsuAutoGenerator : OsuAutoGeneratorBase
{ {
public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
#region Parameters #region Parameters
/// <summary> /// <summary>
@ -42,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Replays
#region Construction / Initialisation #region Construction / Initialisation
public OsuAutoGenerator(Beatmap<OsuHitObject> beatmap) public OsuAutoGenerator(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
// Already superhuman, but still somewhat realistic // Already superhuman, but still somewhat realistic

View File

@ -3,7 +3,6 @@
using osuTK; using osuTK;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Replays; using osu.Game.Replays;
@ -12,7 +11,7 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
public abstract class OsuAutoGeneratorBase : AutoGenerator<OsuHitObject> public abstract class OsuAutoGeneratorBase : AutoGenerator
{ {
#region Constants #region Constants
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Replays
protected Replay Replay; protected Replay Replay;
protected List<ReplayFrame> Frames => Replay.Frames; protected List<ReplayFrame> Frames => Replay.Frames;
protected OsuAutoGeneratorBase(Beatmap<OsuHitObject> beatmap) protected OsuAutoGeneratorBase(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
Replay = new Replay(); Replay = new Replay();

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{ {
private int currentIndex; private int currentIndex;
private Shader shader; private IShader shader;
private Texture texture; private Texture texture;
private Vector2 size => texture.Size * Scale; private Vector2 size => texture.Size * Scale;
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override bool IsPresent => true; public override bool IsPresent => true;
private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
private const int max_sprites = 2048; private const int max_sprites = 2048;
private readonly TrailPart[] parts = new TrailPart[max_sprites]; private readonly TrailPart[] parts = new TrailPart[max_sprites];
@ -55,7 +54,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
tNode.Texture = texture; tNode.Texture = texture;
tNode.Size = size; tNode.Size = size;
tNode.Time = time; tNode.Time = time;
tNode.Shared = trailDrawNodeSharedData;
for (int i = 0; i < parts.Length; ++i) for (int i = 0; i < parts.Length; ++i)
if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID)
@ -81,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ShaderManager shaders, TextureStore textures) private void load(ShaderManager shaders, TextureStore textures)
{ {
shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
texture = textures.Get(@"Cursor/cursortrail"); texture = textures.Get(@"Cursor/cursortrail");
Scale = new Vector2(1 / texture.ScaleAdjust); Scale = new Vector2(1 / texture.ScaleAdjust);
} }
@ -167,22 +165,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public bool WasUpdated; public bool WasUpdated;
} }
private class TrailDrawNodeSharedData
{
public VertexBuffer<TexturedTrailVertex> VertexBuffer;
}
private class TrailDrawNode : DrawNode private class TrailDrawNode : DrawNode
{ {
public Shader Shader; public IShader Shader;
public Texture Texture; public Texture Texture;
public float Time; public float Time;
public TrailDrawNodeSharedData Shared;
public readonly TrailPart[] Parts = new TrailPart[max_sprites]; public readonly TrailPart[] Parts = new TrailPart[max_sprites];
public Vector2 Size; public Vector2 Size;
private readonly VertexBuffer<TexturedTrailVertex> vertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
public TrailDrawNode() public TrailDrawNode()
{ {
for (int i = 0; i < max_sprites; i++) for (int i = 0; i < max_sprites; i++)
@ -194,9 +188,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override void Draw(Action<TexturedVertex2D> vertexAction) public override void Draw(Action<TexturedVertex2D> vertexAction)
{ {
if (Shared.VertexBuffer == null)
Shared.VertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
Shader.GetUniform<float>("g_FadeClock").UpdateValue(ref Time); Shader.GetUniform<float>("g_FadeClock").UpdateValue(ref Time);
int updateStart = -1, updateEnd = 0; int updateStart = -1, updateEnd = 0;
@ -218,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
DrawColourInfo.Colour, DrawColourInfo.Colour,
null, null,
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex
{ {
Position = v.Position, Position = v.Position,
TexturePosition = v.TexturePosition, TexturePosition = v.TexturePosition,
@ -230,24 +221,31 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
} }
else if (updateStart != -1) else if (updateStart != -1)
{ {
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
updateStart = -1; updateStart = -1;
} }
} }
// Update all remaining vertices that have been changed. // Update all remaining vertices that have been changed.
if (updateStart != -1) if (updateStart != -1)
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
base.Draw(vertexAction); base.Draw(vertexAction);
Shader.Bind(); Shader.Bind();
Texture.TextureGL.Bind(); Texture.TextureGL.Bind();
Shared.VertexBuffer.Draw(); vertexBuffer.Draw();
Shader.Unbind(); Shader.Unbind();
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
vertexBuffer.Dispose();
}
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]

View File

@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.UI.Cursor namespace osu.Game.Rulesets.Osu.UI.Cursor
{ {
public class GameplayCursor : CursorContainer, IKeyBindingHandler<OsuAction> public class GameplayCursorContainer : CursorContainer, IKeyBindingHandler<OsuAction>
{ {
protected override Drawable CreateCursor() => new OsuCursor(); protected override Drawable CreateCursor() => new OsuCursor();
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Container<Drawable> fadeContainer; private readonly Container<Drawable> fadeContainer;
public GameplayCursor() public GameplayCursorContainer()
{ {
InternalChild = fadeContainer = new Container InternalChild = fadeContainer = new Container
{ {
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public OsuCursor() public OsuCursor()
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2(42); Size = new Vector2(28);
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)

View File

@ -10,7 +10,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Cursor;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.UI.Cursor;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
@ -22,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.UI
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
private readonly PlayfieldAdjustmentContainer adjustmentContainer;
protected override Container CursorTargetContainer => adjustmentContainer;
protected override CursorContainer CreateCursor() => new GameplayCursorContainer();
public OsuPlayfield() public OsuPlayfield()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
@ -29,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI
Size = new Vector2(0.75f); Size = new Vector2(0.75f);
InternalChild = new PlayfieldAdjustmentContainer InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
@ -13,7 +12,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -59,7 +57,5 @@ namespace osu.Game.Rulesets.Osu.UI
return first.StartTime - first.TimePreempt; return first.StartTime - first.TimePreempt;
} }
} }
protected override CursorContainer CreateCursor() => new GameplayCursor();
} }
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModAutoplay : ModAutoplay<TaikoHitObject> public class TaikoModAutoplay : ModAutoplay<TaikoHitObject>
{ {
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{ {
ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
Replay = new TaikoAutoGenerator(beatmap).Generate(), Replay = new TaikoAutoGenerator(beatmap).Generate(),

View File

@ -9,14 +9,17 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {
public class TaikoAutoGenerator : AutoGenerator<TaikoHitObject> public class TaikoAutoGenerator : AutoGenerator
{ {
public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap;
private const double swell_hit_speed = 50; private const double swell_hit_speed = 50;
public TaikoAutoGenerator(Beatmap<TaikoHitObject> beatmap) public TaikoAutoGenerator(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
Replay = new Replay(); Replay = new Replay();

View File

@ -11,6 +11,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Taiko
new MultiMod(new TaikoModAutoplay(), new ModCinema()), new MultiMod(new TaikoModAutoplay(), new ModCinema()),
new TaikoModRelax(), new TaikoModRelax(),
}; };
case ModType.Fun:
return new Mod[]
{
new MultiMod(new ModWindUp<TaikoHitObject>(), new ModWindDown<TaikoHitObject>())
};
default: default:
return new Mod[] { }; return new Mod[] { };
} }

View File

@ -113,6 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestCase(normal)] [TestCase(normal)]
[TestCase(marathon)] [TestCase(marathon)]
[Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")]
// Currently fails: // Currently fails:
// [TestCase(with_sb)] // [TestCase(with_sb)]
public void TestParity(string beatmap) public void TestParity(string beatmap)

View File

@ -1,10 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -14,15 +16,27 @@ namespace osu.Game.Tests.Visual
{ {
protected override Player CreatePlayer(Ruleset ruleset) protected override Player CreatePlayer(Ruleset ruleset)
{ {
// We create a dummy RulesetContainer just to get the replay - we don't want to use mods here var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo);
// to simulate setting a replay rather than having the replay already set for us
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value);
// Reset the mods return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); }
return new ReplayPlayer(dummyRulesetContainer.ReplayScore); protected override void AddCheckSteps(Func<Player> player)
{
base.AddCheckSteps(player);
AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero");
AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys");
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public ScoreAccessibleReplayPlayer(Score score)
: base(score)
{
}
} }
} }
} }

View File

@ -24,10 +24,13 @@ namespace osu.Game.Tests.Visual
public TestCaseToolbar() public TestCaseToolbar()
{ {
var toolbar = new Toolbar { State = Visibility.Visible }; var toolbar = new Toolbar { State = Visibility.Visible };
ToolbarNotificationButton notificationButton = null;
Add(toolbar); AddStep("create toolbar", () =>
{
var notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First(); Add(toolbar);
notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
});
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual
{ {
public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase
{ {
private UpdateableBeatmapBackgroundSprite backgroundSprite; private TestUpdateableBeatmapBackgroundSprite backgroundSprite;
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private BeatmapManager beatmaps { get; set; }
@ -28,30 +28,36 @@ namespace osu.Game.Tests.Visual
var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); var imported = ImportBeatmapTest.LoadOszIntoOsu(osu);
Child = backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
backgroundSprite.Beatmap.BindTo(beatmapBindable); backgroundSprite.Beatmap.BindTo(beatmapBindable);
var req = new GetBeatmapSetRequest(1); var req = new GetBeatmapSetRequest(1);
api.Queue(req); api.Queue(req);
AddStep("null", () => beatmapBindable.Value = null); AddStep("load null beatmap", () => beatmapBindable.Value = null);
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
AddStep("imported", () => beatmapBindable.Value = imported.Beatmaps.First()); AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First());
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
if (api.IsLoggedIn) if (api.IsLoggedIn)
{ {
AddUntilStep(() => req.Result != null, "wait for api response"); AddUntilStep(() => req.Result != null, "wait for api response");
AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo
AddStep("online", () => beatmapBindable.Value = new BeatmapInfo
{ {
BeatmapSet = req.Result?.ToBeatmapSet(rulesets) BeatmapSet = req.Result?.ToBeatmapSet(rulesets)
}); });
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
} }
else else
{ {
AddStep("online (login first)", () => { }); AddStep("online (login first)", () => { });
} }
} }
private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
{
public int ChildCount => InternalChildren.Count;
}
} }
} }

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
/// <summary> /// <summary>
/// Display a baetmap background from a local source, but fallback to online source if not available. /// Display a beatmap background from a local source, but fallback to online source if not available.
/// </summary> /// </summary>
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<BeatmapInfo> public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<BeatmapInfo>
{ {
@ -18,34 +18,59 @@ namespace osu.Game.Beatmaps.Drawables
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private BeatmapManager beatmaps { get; set; }
public UpdateableBeatmapBackgroundSprite() private readonly BeatmapSetCoverType beatmapSetCoverType;
public UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{ {
Beatmap.BindValueChanged(b => Model = b.NewValue); Beatmap.BindValueChanged(b => Model = b.NewValue);
this.beatmapSetCoverType = beatmapSetCoverType;
}
private BeatmapInfo lastModel;
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad)
{
return new DelayedLoadUnloadWrapper(() =>
{
// If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was
// previously UNLOADED and thus its children have been disposed of, so we need to recreate them here.
if (lastModel == Beatmap.Value && Beatmap.Value != null)
return CreateDrawable(Beatmap.Value);
// If the model has changed since the previous unload (or if there was no load), then we can safely use the given content
lastModel = Beatmap.Value;
return content;
}, timeBeforeLoad, 10000);
} }
protected override Drawable CreateDrawable(BeatmapInfo model) protected override Drawable CreateDrawable(BeatmapInfo model)
{ {
return new DelayedLoadUnloadWrapper(() => Drawable drawable;
var localBeatmap = beatmaps.GetWorkingBeatmap(model);
if (model?.BeatmapSet?.OnlineInfo != null)
{ {
Drawable drawable; drawable = new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType);
}
else if (localBeatmap.BeatmapInfo.ID != 0)
{
// Fall back to local background if one exists
drawable = new BeatmapBackgroundSprite(localBeatmap);
}
else
{
// Use the default background if somehow an online set does not exist and we don't have a local copy.
drawable = new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
}
var localBeatmap = beatmaps.GetWorkingBeatmap(model); drawable.RelativeSizeAxes = Axes.Both;
drawable.Anchor = Anchor.Centre;
drawable.Origin = Anchor.Centre;
drawable.FillMode = FillMode.Fill;
drawable.OnLoadComplete = d => d.FadeInFromZero(400);
if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null) return drawable;
drawable = new BeatmapSetCover(model.BeatmapSet);
else
drawable = new BeatmapBackgroundSprite(localBeatmap);
drawable.RelativeSizeAxes = Axes.Both;
drawable.Anchor = Anchor.Centre;
drawable.Origin = Anchor.Centre;
drawable.FillMode = FillMode.Fill;
drawable.OnLoadComplete = d => d.FadeInFromZero(400);
return drawable;
}, 500, 10000);
} }
protected override double FadeDuration => 0;
} }
} }

View File

@ -64,7 +64,7 @@ namespace osu.Game.Graphics.Backgrounds
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default); private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
private Shader shader; private IShader shader;
private readonly Texture texture; private readonly Texture texture;
public Triangles() public Triangles()
@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Backgrounds
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ShaderManager shaders) private void load(ShaderManager shaders)
{ {
shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -180,8 +180,6 @@ namespace osu.Game.Graphics.Backgrounds
protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
protected override void ApplyDrawNode(DrawNode node) protected override void ApplyDrawNode(DrawNode node)
{ {
base.ApplyDrawNode(node); base.ApplyDrawNode(node);
@ -191,27 +189,21 @@ namespace osu.Game.Graphics.Backgrounds
trianglesNode.Shader = shader; trianglesNode.Shader = shader;
trianglesNode.Texture = texture; trianglesNode.Texture = texture;
trianglesNode.Size = DrawSize; trianglesNode.Size = DrawSize;
trianglesNode.Shared = sharedData;
trianglesNode.Parts.Clear(); trianglesNode.Parts.Clear();
trianglesNode.Parts.AddRange(parts); trianglesNode.Parts.AddRange(parts);
} }
private class TrianglesDrawNodeSharedData
{
public readonly LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
}
private class TrianglesDrawNode : DrawNode private class TrianglesDrawNode : DrawNode
{ {
public Shader Shader; public IShader Shader;
public Texture Texture; public Texture Texture;
public TrianglesDrawNodeSharedData Shared;
public readonly List<TriangleParticle> Parts = new List<TriangleParticle>(); public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
public Vector2 Size; public Vector2 Size;
private readonly LinearBatch<TexturedVertex2D> vertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
public override void Draw(Action<TexturedVertex2D> vertexAction) public override void Draw(Action<TexturedVertex2D> vertexAction)
{ {
base.Draw(vertexAction); base.Draw(vertexAction);
@ -239,12 +231,19 @@ namespace osu.Game.Graphics.Backgrounds
triangle, triangle,
colourInfo, colourInfo,
null, null,
Shared.VertexBatch.AddAction, vertexBatch.AddAction,
Vector2.Divide(localInflationAmount, size)); Vector2.Divide(localInflationAmount, size));
} }
Shader.Unbind(); Shader.Unbind();
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
vertexBatch.Dispose();
}
} }
protected struct TriangleParticle : IComparable<TriangleParticle> protected struct TriangleParticle : IComparable<TriangleParticle>

View File

@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Masking = true, Masking = true,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1 } Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 }
}); });
} }
@ -102,9 +102,10 @@ namespace osu.Game.Graphics.UserInterface
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; // Make sure that we are accounting for path width when calculating vertex positions
float y = GetYPosition(values[i]) * DrawHeight - 1; // We need to apply 2x the path radius to account for it because the full diameter of the line accounts into height
// the -1 is for inner offset in path (actually -PathWidth) float x = (i + count - values.Length) / (float)(count - 1) * (DrawWidth - 2 * path.PathRadius);
float y = GetYPosition(values[i]) * (DrawHeight - 2 * path.PathRadius);
path.AddVertex(new Vector2(x, y)); path.AddVertex(new Vector2(x, y));
} }
} }

View File

@ -70,13 +70,15 @@ namespace osu.Game.Online.API
internal new void Schedule(Action action) => base.Schedule(action); internal new void Schedule(Action action) => base.Schedule(action);
/// <summary>
/// Register a component to receive API events.
/// Fires <see cref="IOnlineComponent.APIStateChanged"/> once immediately to ensure a correct state.
/// </summary>
/// <param name="component"></param>
public void Register(IOnlineComponent component) public void Register(IOnlineComponent component)
{ {
Scheduler.Add(delegate Scheduler.Add(delegate { components.Add(component); });
{ component.APIStateChanged(this, state);
components.Add(component);
component.APIStateChanged(this, state);
});
} }
public void Unregister(IOnlineComponent component) public void Unregister(IOnlineComponent component)

View File

@ -124,6 +124,7 @@ namespace osu.Game.Online.Multiplayer
if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
Host.Value = other.Host.Value; Host.Value = other.Host.Value;
ChannelId.Value = other.ChannelId.Value;
Status.Value = other.Status.Value; Status.Value = other.Status.Value;
Availability.Value = other.Availability.Value; Availability.Value = other.Availability.Value;
Type.Value = other.Type.Value; Type.Value = other.Type.Value;

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar
public Action OnHome; public Action OnHome;
private readonly ToolbarUserArea userArea; private ToolbarUserArea userArea;
protected override bool BlockPositionalInput => false; protected override bool BlockPositionalInput => false;
@ -34,6 +34,13 @@ namespace osu.Game.Overlays.Toolbar
private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
public Toolbar() public Toolbar()
{
RelativeSizeAxes = Axes.X;
Size = new Vector2(1, HEIGHT);
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame osuGame)
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
@ -76,13 +83,6 @@ namespace osu.Game.Overlays.Toolbar
} }
}; };
RelativeSizeAxes = Axes.X;
Size = new Vector2(1, HEIGHT);
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame osuGame)
{
StateChanged += visibility => StateChanged += visibility =>
{ {
if (overlayActivationMode.Value == OverlayActivation.Disabled) if (overlayActivationMode.Value == OverlayActivation.Disabled)

View File

@ -14,10 +14,6 @@ namespace osu.Game.Rulesets.Mods
public abstract class ModAutoplay<T> : ModAutoplay, IApplicableToRulesetContainer<T> public abstract class ModAutoplay<T> : ModAutoplay, IApplicableToRulesetContainer<T>
where T : HitObject where T : HitObject
{ {
protected virtual Score CreateReplayScore(Beatmap<T> beatmap) => new Score { Replay = new Replay() };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
public virtual void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap)); public virtual void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap));
} }
@ -31,5 +27,9 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public bool AllowFail => false; public bool AllowFail => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
public virtual Score CreateReplayScore(IBeatmap beatmap) => new Score { Replay = new Replay() };
} }
} }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override string Description => "Zoooooooooom..."; public override string Description => "Zoooooooooom...";
public override bool Ranked => true; public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime), typeof(ModTimeRamp) };
public virtual void ApplyToClock(IAdjustableClock clock) public virtual void ApplyToClock(IAdjustableClock clock)
{ {

View File

@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mods
public abstract class Flashlight : Drawable public abstract class Flashlight : Drawable
{ {
internal BindableInt Combo; internal BindableInt Combo;
private Shader shader; private IShader shader;
protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); protected override DrawNode CreateDrawNode() => new FlashlightDrawNode();
@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Mods
private class FlashlightDrawNode : DrawNode private class FlashlightDrawNode : DrawNode
{ {
public Shader Shader; public IShader Shader;
public Quad ScreenSpaceDrawQuad; public Quad ScreenSpaceDrawQuad;
public Vector2 FlashlightPosition; public Vector2 FlashlightPosition;
public Vector2 FlashlightSize; public Vector2 FlashlightSize;

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyReduction; public override ModType Type => ModType.DifficultyReduction;
public override string Description => "Less zoom..."; public override string Description => "Less zoom...";
public override bool Ranked => true; public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime), typeof(ModTimeRamp) };
public virtual void ApplyToClock(IAdjustableClock clock) public virtual void ApplyToClock(IAdjustableClock clock)
{ {

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModTimeRamp : Mod
{
public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime), typeof(ModHalfTime) };
protected abstract double FinalRateAdjustment { get; }
}
public abstract class ModTimeRamp<T> : ModTimeRamp, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap<T>
where T : HitObject
{
private double finalRateTime;
private double beginRampTime;
private IAdjustableClock clock;
private IHasPitchAdjust pitchAdjust;
/// <summary>
/// The point in the beatmap at which the final ramping rate should be reached.
/// </summary>
private const double final_rate_progress = 0.75f;
public virtual void ApplyToClock(IAdjustableClock clock)
{
this.clock = clock;
pitchAdjust = (IHasPitchAdjust)clock;
// for preview purposes
pitchAdjust.PitchAdjust = 1.0 + FinalRateAdjustment;
}
public virtual void ApplyToBeatmap(Beatmap<T> beatmap)
{
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0;
finalRateTime = final_rate_progress * ((lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0);
}
public virtual void Update(Playfield playfield)
{
var absRate = Math.Abs(FinalRateAdjustment);
var adjustment = MathHelper.Clamp(absRate * ((clock.CurrentTime - beginRampTime) / finalRateTime), 0, absRate);
pitchAdjust.PitchAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustment;
}
}
}

View 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.Game.Graphics;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mods
{
public class ModWindDown<T> : ModTimeRamp<T>
where T : HitObject
{
public override string Name => "Wind Down";
public override string Acronym => "WD";
public override string Description => "Sloooow doooown...";
public override FontAwesome Icon => FontAwesome.fa_chevron_circle_down;
public override double ScoreMultiplier => 1.0;
protected override double FinalRateAdjustment => -0.25;
}
}

View 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.Game.Graphics;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mods
{
public class ModWindUp<T> : ModTimeRamp<T>
where T : HitObject
{
public override string Name => "Wind Up";
public override string Acronym => "WU";
public override string Description => "Can you keep up?";
public override FontAwesome Icon => FontAwesome.fa_chevron_circle_up;
public override double ScoreMultiplier => 1.0;
protected override double FinalRateAdjustment => 0.5;
}
}

View File

@ -1,14 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Replays; using osu.Game.Replays;
namespace osu.Game.Rulesets.Replays namespace osu.Game.Rulesets.Replays
{ {
public abstract class AutoGenerator<T> : IAutoGenerator public abstract class AutoGenerator : IAutoGenerator
where T : HitObject
{ {
/// <summary> /// <summary>
/// Creates the auto replay and returns it. /// Creates the auto replay and returns it.
@ -21,11 +19,11 @@ namespace osu.Game.Rulesets.Replays
/// <summary> /// <summary>
/// The beatmap we're making. /// The beatmap we're making.
/// </summary> /// </summary>
protected Beatmap<T> Beatmap; protected IBeatmap Beatmap;
#endregion #endregion
protected AutoGenerator(Beatmap<T> beatmap) protected AutoGenerator(IBeatmap beatmap)
{ {
Beatmap = beatmap; Beatmap = beatmap;
} }

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets
/// <returns>An enumerable of constructed <see cref="Mod"/>s</returns> /// <returns>An enumerable of constructed <see cref="Mod"/>s</returns>
public virtual IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; public virtual IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) => new Mod[] { };
public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().First();
protected Ruleset(RulesetInfo rulesetInfo = null) protected Ruleset(RulesetInfo rulesetInfo = null)
{ {

View File

@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osuTK; using osuTK;
@ -63,6 +64,10 @@ namespace osu.Game.Rulesets.UI
private void load(IBindable<WorkingBeatmap> beatmap) private void load(IBindable<WorkingBeatmap> beatmap)
{ {
this.beatmap = beatmap.Value; this.beatmap = beatmap.Value;
Cursor = CreateCursor();
if (Cursor != null)
CursorTargetContainer.Add(Cursor);
} }
/// <summary> /// <summary>
@ -82,6 +87,23 @@ namespace osu.Game.Rulesets.UI
/// <param name="h">The DrawableHitObject to remove.</param> /// <param name="h">The DrawableHitObject to remove.</param>
public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h);
/// <summary>
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
/// </summary>
public CursorContainer Cursor { get; private set; }
/// <summary>
/// Provide an optional cursor which is to be used for gameplay.
/// If providing a cursor, <see cref="CursorTargetContainer"/> must also point to a valid target container.
/// </summary>
/// <returns>The cursor, or null if a cursor is not rqeuired.</returns>
protected virtual CursorContainer CreateCursor() => null;
/// <summary>
/// The target container to add the cursor after it is created.
/// </summary>
protected virtual Container CursorTargetContainer => null;
/// <summary> /// <summary>
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>. /// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
/// This does not add the <see cref="Playfield"/> to the draw hierarchy. /// This does not add the <see cref="Playfield"/> to the draw hierarchy.

View File

@ -16,7 +16,9 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Replays; using osu.Game.Replays;
@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.UI
/// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead. /// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead.
/// </para> /// </para>
/// </summary> /// </summary>
public abstract class RulesetContainer : Container public abstract class RulesetContainer : Container, IProvideCursor
{ {
/// <summary> /// <summary>
/// The selected variant. /// The selected variant.
@ -74,10 +76,11 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public Container Overlays { get; protected set; } public Container Overlays { get; protected set; }
/// <summary> public CursorContainer Cursor => Playfield.Cursor;
/// The cursor provided by this <see cref="RulesetContainer"/>. May be null if no cursor is provided.
/// </summary> public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value;
public readonly CursorContainer Cursor;
protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor
public readonly Ruleset Ruleset; public readonly Ruleset Ruleset;
@ -101,8 +104,6 @@ namespace osu.Game.Rulesets.UI
KeyBindingInputManager.UseParentInput = !paused.NewValue; KeyBindingInputManager.UseParentInput = !paused.NewValue;
}; };
Cursor = CreateCursor();
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@ -259,9 +260,6 @@ namespace osu.Game.Rulesets.UI
Playfield Playfield
}); });
if (Cursor != null)
KeyBindingInputManager.Add(Cursor);
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
KeyBindingInputManager, KeyBindingInputManager,

View File

@ -89,7 +89,7 @@ namespace osu.Game.Screens
/// </summary> /// </summary>
public class ShaderPrecompiler : Drawable public class ShaderPrecompiler : Drawable
{ {
private readonly List<Shader> loadTargets = new List<Shader>(); private readonly List<IShader> loadTargets = new List<IShader>();
public bool FinishedCompiling { get; private set; } public bool FinishedCompiling { get; private set; }
@ -106,7 +106,7 @@ namespace osu.Game.Screens
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
} }
protected virtual bool AllLoaded => loadTargets.All(s => s.Loaded); protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded);
protected override void Update() protected override void Update()
{ {

View File

@ -63,7 +63,7 @@ namespace osu.Game.Screens.Menu
private readonly float[] frequencyAmplitudes = new float[256]; private readonly float[] frequencyAmplitudes = new float[256];
private Shader shader; private IShader shader;
private readonly Texture texture; private readonly Texture texture;
public LogoVisualisation() public LogoVisualisation()
@ -131,8 +131,6 @@ namespace osu.Game.Screens.Menu
protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); protected override DrawNode CreateDrawNode() => new VisualisationDrawNode();
private readonly VisualiserSharedData sharedData = new VisualiserSharedData();
protected override void ApplyDrawNode(DrawNode node) protected override void ApplyDrawNode(DrawNode node)
{ {
base.ApplyDrawNode(node); base.ApplyDrawNode(node);
@ -142,29 +140,23 @@ namespace osu.Game.Screens.Menu
visNode.Shader = shader; visNode.Shader = shader;
visNode.Texture = texture; visNode.Texture = texture;
visNode.Size = DrawSize.X; visNode.Size = DrawSize.X;
visNode.Shared = sharedData;
visNode.Colour = AccentColour; visNode.Colour = AccentColour;
visNode.AudioData = frequencyAmplitudes; visNode.AudioData = frequencyAmplitudes;
} }
private class VisualiserSharedData
{
public readonly QuadBatch<TexturedVertex2D> VertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
}
private class VisualisationDrawNode : DrawNode private class VisualisationDrawNode : DrawNode
{ {
public Shader Shader; public IShader Shader;
public Texture Texture; public Texture Texture;
public VisualiserSharedData Shared;
//Asuming the logo is a circle, we don't need a second dimension. //Asuming the logo is a circle, we don't need a second dimension.
public float Size; public float Size;
public Color4 Colour; public Color4 Colour;
public float[] AudioData; public float[] AudioData;
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
public override void Draw(Action<TexturedVertex2D> vertexAction) public override void Draw(Action<TexturedVertex2D> vertexAction)
{ {
base.Draw(vertexAction); base.Draw(vertexAction);
@ -209,7 +201,7 @@ namespace osu.Game.Screens.Menu
rectangle, rectangle,
colourInfo, colourInfo,
null, null,
Shared.VertexBatch.AddAction, vertexBatch.AddAction,
//barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. //barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that.
Vector2.Divide(inflation, barSize.Yx)); Vector2.Divide(inflation, barSize.Yx));
} }
@ -218,6 +210,13 @@ namespace osu.Game.Screens.Menu
Shader.Unbind(); Shader.Unbind();
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
vertexBatch.Dispose();
}
} }
} }
} }

View File

@ -9,6 +9,13 @@ namespace osu.Game.Screens.Multi.Components
{ {
public class MultiplayerBackgroundSprite : MultiplayerComposite public class MultiplayerBackgroundSprite : MultiplayerComposite
{ {
private readonly BeatmapSetCoverType beatmapSetCoverType;
public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
this.beatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -19,6 +26,6 @@ namespace osu.Game.Screens.Multi.Components
CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true); CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true);
} }
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -137,7 +138,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
Width = cover_width, Width = cover_width,
Masking = true, Masking = true,
Margin = new MarginPadding { Left = side_strip_width }, Margin = new MarginPadding { Left = side_strip_width },
Child = new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both } Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
}, },
new Container new Container
{ {

View File

@ -107,6 +107,14 @@ namespace osu.Game.Screens.Multi.Lounge
Filter.Search.HoldFocus = false; Filter.Search.HoldFocus = false;
} }
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
if (currentRoom.Value?.RoomID.Value == null)
currentRoom.Value = new Room();
}
private void joinRequested(Room room) private void joinRequested(Room room)
{ {
processingOverlay.Show(); processingOverlay.Show();

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osuTK; using osuTK;
@ -108,7 +110,7 @@ namespace osu.Game.Screens.Multi.Match.Components
}, },
}; };
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods, true); CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty<Mod>(), true);
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
} }

View File

@ -28,13 +28,15 @@ namespace osu.Game.Screens.Multi.Match.Components
{ {
base.LoadComplete(); base.LoadComplete();
roomId.BindValueChanged(_ => updateChannel(), true); channelId.BindValueChanged(_ => updateChannel(), true);
} }
private void updateChannel() private void updateChannel()
{ {
if (roomId.Value != null) if (roomId.Value == null || channelId.Value == 0)
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#mp_{roomId.Value}" }); return;
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" });
} }
} }
} }

View File

@ -0,0 +1,151 @@
// 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.Linq;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.Play
{
/// <summary>
/// Encapsulates gameplay timing logic and provides a <see cref="GameplayClock"/> for children.
/// </summary>
public class GameplayClockContainer : Container
{
private readonly WorkingBeatmap beatmap;
/// <summary>
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
/// </summary>
private readonly IAdjustableClock sourceClock;
public readonly BindableBool IsPaused = new BindableBool();
/// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
};
/// <summary>
/// The final clock which is exposed to underlying components.
/// </summary>
[Cached]
private readonly GameplayClock gameplayClock;
private Bindable<double> userAudioOffset;
private readonly FramedOffsetClock offsetClock;
public GameplayClockContainer(WorkingBeatmap beatmap, bool allowLeadIn, double gameplayStartTime)
{
this.beatmap = beatmap;
RelativeSizeAxes = Axes.Both;
sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock();
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.Seek(allowLeadIn
? Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)
: gameplayStartTime);
adjustableClock.ProcessFrame();
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
// the final usable gameplay clock with user-set offsets applied.
offsetClock = new FramedOffsetClock(platformOffsetClock);
// the clock to be exposed via DI to children.
gameplayClock = new GameplayClock(offsetClock);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true);
UserPlaybackRate.ValueChanged += _ => updateRate();
}
public void Restart()
{
Task.Run(() =>
{
sourceClock.Reset();
Schedule(() =>
{
adjustableClock.ChangeSource(sourceClock);
updateRate();
this.Delay(750).Schedule(() =>
{
if (!IsPaused.Value)
{
adjustableClock.Start();
}
});
});
});
}
public void Start()
{
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
adjustableClock.Seek(adjustableClock.CurrentTime);
adjustableClock.Start();
}
public void Seek(double time) => adjustableClock.Seek(time);
public void Stop() => adjustableClock.Stop();
public void ResetLocalAdjustments()
{
// In the case of replays, we may have changed the playback rate.
UserPlaybackRate.Value = 1;
}
protected override void Update()
{
if (!IsPaused.Value)
offsetClock.ProcessFrame();
base.Update();
}
private void updateRate()
{
if (sourceClock == null) return;
sourceClock.Rate = 1;
foreach (var mod in beatmap.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(sourceClock);
sourceClock.Rate *= UserPlaybackRate.Value;
}
}
}

View File

@ -19,7 +19,9 @@ namespace osu.Game.Screens.Play.HUD
public readonly PlaybackSettings PlaybackSettings; public readonly PlaybackSettings PlaybackSettings;
public readonly VisualSettings VisualSettings; public readonly VisualSettings VisualSettings;
//public readonly CollectionSettings CollectionSettings; //public readonly CollectionSettings CollectionSettings;
//public readonly DiscussionSettings DiscussionSettings; //public readonly DiscussionSettings DiscussionSettings;
public PlayerSettingsOverlay() public PlayerSettingsOverlay()

View File

@ -1,12 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -40,7 +40,9 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce; private static bool hasShownNotificationOnce;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IAdjustableClock adjustableClock) public Action<double> RequestSeek;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -92,11 +94,9 @@ namespace osu.Game.Screens.Play
Progress.Objects = rulesetContainer.Objects; Progress.Objects = rulesetContainer.Objects;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
Progress.RequestSeek = pos => adjustableClock.Seek(pos); Progress.RequestSeek = time => RequestSeek(time);
ModDisplay.Current.BindTo(working.Mods); ModDisplay.Current.BindTo(working.Mods);
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]

View File

@ -7,16 +7,13 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
/// <summary> /// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices and processing the clock. /// A container which handles pausing children, displaying an overlay blocking its children during paused state.
/// Exposes a <see cref="GameplayClock"/> to children via DI.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary> /// </summary>
public class PausableGameplayContainer : Container public class PausableGameplayContainer : Container
{ {
@ -44,46 +41,33 @@ namespace osu.Game.Screens.Play
public Action OnRetry; public Action OnRetry;
public Action OnQuit; public Action OnQuit;
private readonly FramedClock offsetClock; public Action Stop;
private readonly DecoupleableInterpolatingFramedClock adjustableClock; public Action Start;
/// <summary>
/// The final clock which is exposed to underlying components.
/// </summary>
[Cached]
private readonly GameplayClock gameplayClock;
/// <summary> /// <summary>
/// Creates a new <see cref="PausableGameplayContainer"/>. /// Creates a new <see cref="PausableGameplayContainer"/>.
/// </summary> /// </summary>
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param> public PausableGameplayContainer()
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
public PausableGameplayContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock)
{ {
this.offsetClock = offsetClock;
this.adjustableClock = adjustableClock;
gameplayClock = new GameplayClock(offsetClock);
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
AddInternal(content = new Container InternalChildren = new[]
{ {
Clock = this.offsetClock, content = new Container
ProcessCustomClock = false,
RelativeSizeAxes = Axes.Both
});
AddInternal(pauseOverlay = new PauseOverlay
{
OnResume = () =>
{ {
IsResuming = true; RelativeSizeAxes = Axes.Both
this.Delay(400).Schedule(Resume);
}, },
OnRetry = () => OnRetry(), pauseOverlay = new PauseOverlay
OnQuit = () => OnQuit(), {
}); OnResume = () =>
{
IsResuming = true;
this.Delay(400).Schedule(Resume);
},
OnRetry = () => OnRetry(),
OnQuit = () => OnQuit(),
}
};
} }
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
@ -93,7 +77,7 @@ namespace osu.Game.Screens.Play
if (IsPaused.Value) return; if (IsPaused.Value) return;
// stop the seekable clock (stops the audio eventually) // stop the seekable clock (stops the audio eventually)
adjustableClock.Stop(); Stop?.Invoke();
IsPaused.Value = true; IsPaused.Value = true;
pauseOverlay.Show(); pauseOverlay.Show();
@ -105,14 +89,12 @@ namespace osu.Game.Screens.Play
{ {
if (!IsPaused.Value) return; if (!IsPaused.Value) return;
IsPaused.Value = false;
IsResuming = false; IsResuming = false;
lastPauseActionTime = Time.Current; lastPauseActionTime = Time.Current;
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time IsPaused.Value = false;
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
adjustableClock.Seek(adjustableClock.CurrentTime); Start?.Invoke();
adjustableClock.Start();
pauseOverlay.Hide(); pauseOverlay.Hide();
} }
@ -131,9 +113,6 @@ namespace osu.Game.Screens.Play
if (!game.IsActive.Value && CanPause) if (!game.IsActive.Value && CanPause)
Pause(); Pause();
if (!IsPaused.Value)
offsetClock.ProcessFrame();
base.Update(); base.Update();
} }

View File

@ -3,24 +3,19 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -34,7 +29,7 @@ using osu.Game.Storyboards.Drawables;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
public class Player : ScreenWithBeatmapBackground, IProvideCursor public class Player : ScreenWithBeatmapBackground
{ {
protected override bool AllowBackButton => false; // handled by HoldForMenuButton protected override bool AllowBackButton => false; // handled by HoldForMenuButton
@ -53,22 +48,11 @@ namespace osu.Game.Screens.Play
public bool AllowResults { get; set; } = true; public bool AllowResults { get; set; } = true;
private Bindable<bool> mouseWheelDisabled; private Bindable<bool> mouseWheelDisabled;
private Bindable<double> userAudioOffset;
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>(); private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
public int RestartCount; public int RestartCount;
public CursorContainer Cursor => RulesetContainer.Cursor;
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
private IAdjustableClock sourceClock;
/// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private DecoupleableInterpolatingFramedClock adjustableClock;
[Resolved] [Resolved]
private ScoreManager scoreManager { get; set; } private ScoreManager scoreManager { get; set; }
@ -98,25 +82,113 @@ namespace osu.Game.Screens.Play
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
private GameplayClockContainer gameplayClockContainer;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, APIAccess api, OsuConfigManager config) private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
{ {
this.api = api; this.api = api;
WorkingBeatmap working = Beatmap.Value; WorkingBeatmap working = loadBeatmap();
if (working is DummyWorkingBeatmap)
if (working == null)
return; return;
sampleRestart = audio.Sample.Get(@"Gameplay/restart"); sampleRestart = audio.Sample.Get(@"Gameplay/restart");
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel); mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
IBeatmap beatmap; ScoreProcessor = RulesetContainer.CreateScoreProcessor();
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime);
gameplayClockContainer.Children = new Drawable[]
{
PausableGameplayContainer = new PausableGameplayContainer
{
Retries = RestartCount,
OnRetry = restart,
OnQuit = performUserRequestedExit,
Start = gameplayClockContainer.Start,
Stop = gameplayClockContainer.Stop,
IsPaused = { BindTarget = gameplayClockContainer.IsPaused },
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
Children = new[]
{
StoryboardContainer = CreateStoryboardContainer(),
new ScalingContainer(ScalingMode.Gameplay)
{
Child = new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
}
},
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Breaks = working.Beatmap.Breaks
},
// display the cursor above some HUD elements.
RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working)
{
HoldToQuit = { Action = performUserRequestedExit },
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } },
KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } },
RequestSeek = gameplayClockContainer.Seek,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(RulesetContainer.GameplayStartTime)
{
RequestSeek = gameplayClockContainer.Seek
},
}
},
failOverlay = new FailOverlay
{
OnRetry = restart,
OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
{
Action = () =>
{
if (!this.IsCurrentScreen()) return;
fadeOut(true);
restart();
},
}
};
// bind clock into components that require it
RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused);
if (ShowStoryboard.Value)
initializeStoryboard(false);
// Bind ScoreProcessor to ourselves
ScoreProcessor.AllJudged += onCompletion;
ScoreProcessor.Failed += onFail;
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
}
private WorkingBeatmap loadBeatmap()
{
WorkingBeatmap working = Beatmap.Value;
if (working is DummyWorkingBeatmap)
return null;
try try
{ {
beatmap = working.Beatmap; var beatmap = working.Beatmap;
if (beatmap == null) if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded"); throw new InvalidOperationException("Beatmap was not loaded");
@ -140,119 +212,17 @@ namespace osu.Game.Screens.Play
if (!RulesetContainer.Objects.Any()) if (!RulesetContainer.Objects.Any())
{ {
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
return; return null;
} }
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error(e, "Could not load beatmap sucessfully!"); Logger.Error(e, "Could not load beatmap sucessfully!");
//couldn't load, hard abort! //couldn't load, hard abort!
return; return null;
} }
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); return working;
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.Seek(AllowLeadIn
? Math.Min(0, RulesetContainer.GameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)
: RulesetContainer.GameplayStartTime);
adjustableClock.ProcessFrame();
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
// the final usable gameplay clock with user-set offsets applied.
var offsetClock = new FramedOffsetClock(platformOffsetClock);
userAudioOffset.ValueChanged += offset => offsetClock.Offset = offset.NewValue;
userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
InternalChildren = new Drawable[]
{
PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock)
{
Retries = RestartCount,
OnRetry = restart,
OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
Children = new Container[]
{
StoryboardContainer = CreateStoryboardContainer(),
new ScalingContainer(ScalingMode.Gameplay)
{
Child = new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
}
},
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Breaks = beatmap.Breaks
},
new ScalingContainer(ScalingMode.Gameplay)
{
Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
},
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, adjustableClock)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(RulesetContainer.GameplayStartTime)
{
RequestSeek = time => adjustableClock.Seek(time)
},
}
},
failOverlay = new FailOverlay
{
OnRetry = restart,
OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
{
Action = () =>
{
if (!this.IsCurrentScreen()) return;
fadeOut(true);
restart();
},
}
};
HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused);
if (ShowStoryboard.Value)
initializeStoryboard(false);
// Bind ScoreProcessor to ourselves
ScoreProcessor.AllJudged += onCompletion;
ScoreProcessor.Failed += onFail;
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
}
private void applyRateFromMods()
{
if (sourceClock == null) return;
sourceClock.Rate = 1;
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(sourceClock);
} }
private void performUserRequestedExit() private void performUserRequestedExit()
@ -321,7 +291,7 @@ namespace osu.Game.Screens.Play
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail)) if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false; return false;
adjustableClock.Stop(); gameplayClockContainer.Stop();
HasFailed = true; HasFailed = true;
failOverlay.Retries = RestartCount; failOverlay.Retries = RestartCount;
@ -355,24 +325,7 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
Task.Run(() => gameplayClockContainer.Restart();
{
sourceClock.Reset();
Schedule(() =>
{
adjustableClock.ChangeSource(sourceClock);
applyRateFromMods();
this.Delay(750).Schedule(() =>
{
if (!PausableGameplayContainer.IsPaused.Value)
{
adjustableClock.Start();
}
});
});
});
PausableGameplayContainer.Alpha = 0; PausableGameplayContainer.Alpha = 0;
PausableGameplayContainer.FadeIn(750, Easing.OutQuint); PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
@ -395,8 +348,8 @@ namespace osu.Game.Screens.Play
if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true))
{ {
// In the case of replays, we may have changed the playback rate. gameplayClockContainer.ResetLocalAdjustments();
applyRateFromMods();
fadeOut(); fadeOut();
return base.OnExiting(next); return base.OnExiting(next);
} }

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -16,7 +15,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
protected override string Title => @"playback"; protected override string Title => @"playback";
public IAdjustableClock AdjustableClock { set; get; } public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
};
private readonly PlayerSliderBar<double> rateSlider; private readonly PlayerSliderBar<double> rateSlider;
@ -47,31 +52,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
} }
}, },
}, },
rateSlider = new PlayerSliderBar<double> rateSlider = new PlayerSliderBar<double> { Bindable = UserPlaybackRate }
{
Bindable = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
},
}
}; };
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
if (AdjustableClock == null)
return;
var clockRate = AdjustableClock.Rate;
// can't trigger this line instantly as the underlying clock may not be ready to accept adjustments yet.
rateSlider.Bindable.ValueChanged += multiplier => AdjustableClock.Rate = clockRate * multiplier.NewValue;
rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -60,9 +61,12 @@ namespace osu.Game.Screens.Select
if (base.OnExiting(next)) if (base.OnExiting(next))
return true; return true;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value?.Beatmap); if (CurrentItem.Value != null)
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value?.RequiredMods; {
Ruleset.Value = CurrentItem.Value?.Ruleset; Ruleset.Value = CurrentItem.Value.Ruleset;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty<Mod>();
}
Beatmap.Disabled = true; Beatmap.Disabled = true;
Ruleset.Disabled = true; Ruleset.Disabled = true;

View File

@ -20,14 +20,14 @@ namespace osu.Game.Utils
private readonly List<Task> tasks = new List<Task>(); private readonly List<Task> tasks = new List<Task>();
private Exception lastException;
public RavenLogger(OsuGame game) public RavenLogger(OsuGame game)
{ {
raven.Release = game.Version; raven.Release = game.Version;
if (!game.IsDeployedBuild) return; if (!game.IsDeployedBuild) return;
Exception lastException = null;
Logger.NewEntry += entry => Logger.NewEntry += entry =>
{ {
if (entry.Level < LogLevel.Verbose) return; if (entry.Level < LogLevel.Verbose) return;

View File

@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.301.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.308.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -105,12 +105,12 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.301.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.308.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.301.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.308.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.208.0" ExcludeAssets="all" /> <PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.307.0" ExcludeAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -262,6 +262,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IL/@EntryIndexedValue">IL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IL/@EntryIndexedValue">IL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JIT/@EntryIndexedValue">JIT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LTRB/@EntryIndexedValue">LTRB</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LTRB/@EntryIndexedValue">LTRB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String>