mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:33:30 +08:00
Merge branch 'screen-title-class' of github.com:Jorolf/osu into profile-header-update
This commit is contained in:
commit
bb6e57169f
@ -10,6 +10,7 @@ using osu.Game.Rulesets.UI;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Catch
|
||||
new MultiMod(new CatchModAutoplay(), new ModCinema()),
|
||||
new CatchModRelax(),
|
||||
};
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<CatchHitObject>(), new ModWindDown<CatchHitObject>())
|
||||
};
|
||||
default:
|
||||
return new Mod[] { };
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
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!" } },
|
||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||
|
@ -6,17 +6,20 @@ using System.Linq;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Replays
|
||||
{
|
||||
internal class CatchAutoGenerator : AutoGenerator<CatchHitObject>
|
||||
internal class CatchAutoGenerator : AutoGenerator
|
||||
{
|
||||
public const double RELEASE_DELAY = 20;
|
||||
|
||||
public CatchAutoGenerator(Beatmap<CatchHitObject> beatmap)
|
||||
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
||||
|
||||
public CatchAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
|
@ -12,6 +12,7 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@ -145,6 +146,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
new MultiMod(new ManiaModAutoplay(), new ModCinema()),
|
||||
};
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<ManiaHitObject>(), new ModWindDown<ManiaHitObject>())
|
||||
};
|
||||
default:
|
||||
return new Mod[] { };
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
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!" } },
|
||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||
|
@ -5,13 +5,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Replays
|
||||
{
|
||||
internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject>
|
||||
internal class ManiaAutoGenerator : AutoGenerator
|
||||
{
|
||||
public const double RELEASE_DELAY = 20;
|
||||
|
||||
|
@ -16,18 +16,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
|
||||
{
|
||||
private GameplayCursor cursor;
|
||||
private GameplayCursorContainer cursorContainer;
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
|
||||
|
||||
public CursorContainer Cursor => cursor;
|
||||
public CursorContainer Cursor => cursorContainer;
|
||||
|
||||
public bool ProvidingUserCursor => true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both });
|
||||
Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
path = new SmoothPath
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
PathWidth = 1
|
||||
PathRadius = 1
|
||||
},
|
||||
marker = new CircularContainer
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
InternalChild = body = new ManualSliderBody
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
@ -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 osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
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" } },
|
||||
Replay = new OsuAutoGenerator(beatmap).Generate()
|
||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
Body = new SnakingSliderBody(s)
|
||||
{
|
||||
PathWidth = s.Scale * 64,
|
||||
PathRadius = s.Scale * 64,
|
||||
},
|
||||
ticks = new Container<DrawableSliderTick> { 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);
|
||||
scaleBindable.BindValueChanged(scale =>
|
||||
{
|
||||
Body.PathWidth = scale.NewValue * 64;
|
||||
Body.PathRadius = scale.NewValue * 64;
|
||||
Ball.Scale = new Vector2(scale.NewValue);
|
||||
});
|
||||
|
||||
|
@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
private readonly BufferedContainer container;
|
||||
|
||||
public float PathWidth
|
||||
public float PathRadius
|
||||
{
|
||||
get => path.PathWidth;
|
||||
set => path.PathWidth = value;
|
||||
get => path.PathRadius;
|
||||
set => path.PathRadius = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Overlays.Settings;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@ -128,7 +129,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
new OsuModTransform(),
|
||||
new OsuModWiggle(),
|
||||
new OsuModGrow()
|
||||
new OsuModGrow(),
|
||||
new MultiMod(new ModWindUp<OsuHitObject>(), new ModWindDown<OsuHitObject>()),
|
||||
};
|
||||
default:
|
||||
return new Mod[] { };
|
||||
|
@ -10,12 +10,15 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public class OsuAutoGenerator : OsuAutoGeneratorBase
|
||||
{
|
||||
public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
|
||||
|
||||
#region Parameters
|
||||
|
||||
/// <summary>
|
||||
@ -42,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
#region Construction / Initialisation
|
||||
|
||||
public OsuAutoGenerator(Beatmap<OsuHitObject> beatmap)
|
||||
public OsuAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
// Already superhuman, but still somewhat realistic
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osuTK;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Replays;
|
||||
@ -12,7 +11,7 @@ using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public abstract class OsuAutoGeneratorBase : AutoGenerator<OsuHitObject>
|
||||
public abstract class OsuAutoGeneratorBase : AutoGenerator
|
||||
{
|
||||
#region Constants
|
||||
|
||||
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
protected OsuAutoGeneratorBase(Beatmap<OsuHitObject> beatmap)
|
||||
protected OsuAutoGeneratorBase(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
private int currentIndex;
|
||||
|
||||
private Shader shader;
|
||||
private IShader shader;
|
||||
private Texture texture;
|
||||
|
||||
private Vector2 size => texture.Size * Scale;
|
||||
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
public override bool IsPresent => true;
|
||||
|
||||
private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
|
||||
private const int max_sprites = 2048;
|
||||
|
||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||
@ -55,7 +54,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
tNode.Texture = texture;
|
||||
tNode.Size = size;
|
||||
tNode.Time = time;
|
||||
tNode.Shared = trailDrawNodeSharedData;
|
||||
|
||||
for (int i = 0; i < parts.Length; ++i)
|
||||
if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID)
|
||||
@ -81,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders, TextureStore textures)
|
||||
{
|
||||
shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
|
||||
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
|
||||
texture = textures.Get(@"Cursor/cursortrail");
|
||||
Scale = new Vector2(1 / texture.ScaleAdjust);
|
||||
}
|
||||
@ -167,22 +165,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
public bool WasUpdated;
|
||||
}
|
||||
|
||||
private class TrailDrawNodeSharedData
|
||||
{
|
||||
public VertexBuffer<TexturedTrailVertex> VertexBuffer;
|
||||
}
|
||||
|
||||
private class TrailDrawNode : DrawNode
|
||||
{
|
||||
public Shader Shader;
|
||||
public IShader Shader;
|
||||
public Texture Texture;
|
||||
|
||||
public float Time;
|
||||
public TrailDrawNodeSharedData Shared;
|
||||
|
||||
public readonly TrailPart[] Parts = new TrailPart[max_sprites];
|
||||
public Vector2 Size;
|
||||
|
||||
private readonly VertexBuffer<TexturedTrailVertex> vertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
|
||||
|
||||
public TrailDrawNode()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (Shared.VertexBuffer == null)
|
||||
Shared.VertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
|
||||
|
||||
Shader.GetUniform<float>("g_FadeClock").UpdateValue(ref Time);
|
||||
|
||||
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),
|
||||
DrawColourInfo.Colour,
|
||||
null,
|
||||
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
|
||||
v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex
|
||||
{
|
||||
Position = v.Position,
|
||||
TexturePosition = v.TexturePosition,
|
||||
@ -230,24 +221,31 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
else if (updateStart != -1)
|
||||
{
|
||||
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
updateStart = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update all remaining vertices that have been changed.
|
||||
if (updateStart != -1)
|
||||
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
|
||||
base.Draw(vertexAction);
|
||||
|
||||
Shader.Bind();
|
||||
|
||||
Texture.TextureGL.Bind();
|
||||
Shared.VertexBuffer.Draw();
|
||||
vertexBuffer.Draw();
|
||||
|
||||
Shader.Unbind();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -17,7 +17,7 @@ using osuTK.Graphics;
|
||||
|
||||
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();
|
||||
|
||||
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
private readonly Container<Drawable> fadeContainer;
|
||||
|
||||
public GameplayCursor()
|
||||
public GameplayCursorContainer()
|
||||
{
|
||||
InternalChild = fadeContainer = new Container
|
||||
{
|
||||
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
public OsuCursor()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(42);
|
||||
Size = new Vector2(28);
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
@ -10,7 +10,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
|
||||
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);
|
||||
|
||||
private readonly PlayfieldAdjustmentContainer adjustmentContainer;
|
||||
|
||||
protected override Container CursorTargetContainer => adjustmentContainer;
|
||||
|
||||
protected override CursorContainer CreateCursor() => new GameplayCursorContainer();
|
||||
|
||||
public OsuPlayfield()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
@ -29,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
Size = new Vector2(0.75f);
|
||||
|
||||
InternalChild = new PlayfieldAdjustmentContainer
|
||||
InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Beatmaps;
|
||||
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.Replays;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
@ -59,7 +57,5 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return first.StartTime - first.TimePreempt;
|
||||
}
|
||||
}
|
||||
|
||||
protected override CursorContainer CreateCursor() => new GameplayCursor();
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
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!" } },
|
||||
Replay = new TaikoAutoGenerator(beatmap).Generate(),
|
||||
|
@ -9,14 +9,17 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
|
||||
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;
|
||||
|
||||
public TaikoAutoGenerator(Beatmap<TaikoHitObject> beatmap)
|
||||
public TaikoAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
|
@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Taiko
|
||||
new MultiMod(new TaikoModAutoplay(), new ModCinema()),
|
||||
new TaikoModRelax(),
|
||||
};
|
||||
case ModType.Fun:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ModWindUp<TaikoHitObject>(), new ModWindDown<TaikoHitObject>())
|
||||
};
|
||||
default:
|
||||
return new Mod[] { };
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
[TestCase(normal)]
|
||||
[TestCase(marathon)]
|
||||
[Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")]
|
||||
// Currently fails:
|
||||
// [TestCase(with_sb)]
|
||||
public void TestParity(string beatmap)
|
||||
|
@ -1,10 +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 System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
@ -14,15 +16,27 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
protected override Player CreatePlayer(Ruleset ruleset)
|
||||
{
|
||||
// We create a dummy RulesetContainer just to get the replay - we don't want to use mods here
|
||||
// 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);
|
||||
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
// Reset the mods
|
||||
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay));
|
||||
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,13 @@ namespace osu.Game.Tests.Visual
|
||||
public TestCaseToolbar()
|
||||
{
|
||||
var toolbar = new Toolbar { State = Visibility.Visible };
|
||||
ToolbarNotificationButton notificationButton = null;
|
||||
|
||||
Add(toolbar);
|
||||
|
||||
var notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
|
||||
AddStep("create toolbar", () =>
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase
|
||||
{
|
||||
private UpdateableBeatmapBackgroundSprite backgroundSprite;
|
||||
private TestUpdateableBeatmapBackgroundSprite backgroundSprite;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
@ -28,30 +28,36 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
var imported = ImportBeatmapTest.LoadOszIntoOsu(osu);
|
||||
|
||||
Child = backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
|
||||
Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
backgroundSprite.Beatmap.BindTo(beatmapBindable);
|
||||
|
||||
var req = new GetBeatmapSetRequest(1);
|
||||
api.Queue(req);
|
||||
|
||||
AddStep("null", () => beatmapBindable.Value = null);
|
||||
|
||||
AddStep("imported", () => beatmapBindable.Value = imported.Beatmaps.First());
|
||||
AddStep("load null beatmap", () => beatmapBindable.Value = null);
|
||||
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
|
||||
AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First());
|
||||
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
|
||||
|
||||
if (api.IsLoggedIn)
|
||||
{
|
||||
AddUntilStep(() => req.Result != null, "wait for api response");
|
||||
|
||||
AddStep("online", () => beatmapBindable.Value = new BeatmapInfo
|
||||
AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = req.Result?.ToBeatmapSet(rulesets)
|
||||
});
|
||||
AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup...");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddStep("online (login first)", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||
{
|
||||
public int ChildCount => InternalChildren.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers;
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
/// <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>
|
||||
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<BeatmapInfo>
|
||||
{
|
||||
@ -26,37 +26,51 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
this.beatmapSetCoverType = beatmapSetCoverType;
|
||||
}
|
||||
|
||||
protected override Drawable CreateDrawable(BeatmapInfo model)
|
||||
private BeatmapInfo lastModel;
|
||||
|
||||
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad)
|
||||
{
|
||||
return new DelayedLoadUnloadWrapper(() =>
|
||||
{
|
||||
Drawable drawable;
|
||||
// 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);
|
||||
|
||||
var localBeatmap = beatmaps.GetWorkingBeatmap(model);
|
||||
|
||||
if (model?.BeatmapSet?.OnlineInfo != null)
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
// 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 double FadeDuration => 0;
|
||||
protected override Drawable CreateDrawable(BeatmapInfo model)
|
||||
{
|
||||
Drawable drawable;
|
||||
|
||||
var localBeatmap = beatmaps.GetWorkingBeatmap(model);
|
||||
|
||||
if (model?.BeatmapSet?.OnlineInfo != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
drawable.RelativeSizeAxes = Axes.Both;
|
||||
drawable.Anchor = Anchor.Centre;
|
||||
drawable.Origin = Anchor.Centre;
|
||||
drawable.FillMode = FillMode.Fill;
|
||||
drawable.OnLoadComplete = d => d.FadeInFromZero(400);
|
||||
|
||||
return drawable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
|
||||
|
||||
private Shader shader;
|
||||
private IShader shader;
|
||||
private readonly Texture texture;
|
||||
|
||||
public Triangles()
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[BackgroundDependencyLoader]
|
||||
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()
|
||||
@ -180,8 +180,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
|
||||
|
||||
private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
|
||||
|
||||
protected override void ApplyDrawNode(DrawNode node)
|
||||
{
|
||||
base.ApplyDrawNode(node);
|
||||
@ -191,27 +189,21 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
trianglesNode.Shader = shader;
|
||||
trianglesNode.Texture = texture;
|
||||
trianglesNode.Size = DrawSize;
|
||||
trianglesNode.Shared = sharedData;
|
||||
|
||||
trianglesNode.Parts.Clear();
|
||||
trianglesNode.Parts.AddRange(parts);
|
||||
}
|
||||
|
||||
private class TrianglesDrawNodeSharedData
|
||||
{
|
||||
public readonly LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
|
||||
}
|
||||
|
||||
private class TrianglesDrawNode : DrawNode
|
||||
{
|
||||
public Shader Shader;
|
||||
public IShader Shader;
|
||||
public Texture Texture;
|
||||
|
||||
public TrianglesDrawNodeSharedData Shared;
|
||||
|
||||
public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
|
||||
public Vector2 Size;
|
||||
|
||||
private readonly LinearBatch<TexturedVertex2D> vertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
|
||||
|
||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||
{
|
||||
base.Draw(vertexAction);
|
||||
@ -239,12 +231,19 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
triangle,
|
||||
colourInfo,
|
||||
null,
|
||||
Shared.VertexBatch.AddAction,
|
||||
vertexBatch.AddAction,
|
||||
Vector2.Divide(localInflationAmount, size));
|
||||
}
|
||||
|
||||
Shader.Unbind();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBatch.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected struct TriangleParticle : IComparable<TriangleParticle>
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1.5f }
|
||||
Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 }
|
||||
});
|
||||
}
|
||||
|
||||
@ -109,8 +109,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1;
|
||||
float y = GetYPosition(values[i]) * DrawHeight - path.PathWidth;
|
||||
// Make sure that we are accounting for path width when calculating vertex positions
|
||||
// We need to apply 2x the path radius to account for it because the full diameter of the line accounts into height
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
73
osu.Game/Graphics/UserInterface/ScreenTitle.cs
Normal file
73
osu.Game/Graphics/UserInterface/ScreenTitle.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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class ScreenTitle : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private readonly SpriteIcon iconSprite;
|
||||
private readonly OsuSpriteText titleText, pageText;
|
||||
|
||||
public FontAwesome Icon
|
||||
{
|
||||
get => iconSprite.Icon;
|
||||
set => iconSprite.Icon = value;
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => titleText.Text;
|
||||
set => titleText.Text = value;
|
||||
}
|
||||
|
||||
public string Page
|
||||
{
|
||||
get => pageText.Text;
|
||||
set => pageText.Text = value;
|
||||
}
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => pageText.Colour;
|
||||
set => pageText.Colour = value;
|
||||
}
|
||||
|
||||
public ScreenTitle()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
iconSprite = new SpriteIcon
|
||||
{
|
||||
Size = new Vector2(25),
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding { Right = 10 },
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 25),
|
||||
},
|
||||
pageText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 25),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -70,13 +70,15 @@ namespace osu.Game.Online.API
|
||||
|
||||
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)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Add(component);
|
||||
component.APIStateChanged(this, state);
|
||||
});
|
||||
Scheduler.Add(delegate { components.Add(component); });
|
||||
component.APIStateChanged(this, state);
|
||||
}
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
|
@ -124,6 +124,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
|
||||
Host.Value = other.Host.Value;
|
||||
|
||||
ChannelId.Value = other.ChannelId.Value;
|
||||
Status.Value = other.Status.Value;
|
||||
Availability.Value = other.Availability.Value;
|
||||
Type.Value = other.Type.Value;
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
public Action OnHome;
|
||||
|
||||
private readonly ToolbarUserArea userArea;
|
||||
private ToolbarUserArea userArea;
|
||||
|
||||
protected override bool BlockPositionalInput => false;
|
||||
|
||||
@ -34,6 +34,13 @@ namespace osu.Game.Overlays.Toolbar
|
||||
private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||
|
||||
public Toolbar()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Size = new Vector2(1, HEIGHT);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame osuGame)
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
if (overlayActivationMode.Value == OverlayActivation.Disabled)
|
||||
|
@ -14,10 +14,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public abstract class ModAutoplay<T> : ModAutoplay, IApplicableToRulesetContainer<T>
|
||||
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));
|
||||
}
|
||||
|
||||
@ -31,5 +27,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public bool AllowFail => false;
|
||||
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() };
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override string Description => "Zoooooooooom...";
|
||||
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)
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public abstract class Flashlight : Drawable
|
||||
{
|
||||
internal BindableInt Combo;
|
||||
private Shader shader;
|
||||
private IShader shader;
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new FlashlightDrawNode();
|
||||
|
||||
@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
private class FlashlightDrawNode : DrawNode
|
||||
{
|
||||
public Shader Shader;
|
||||
public IShader Shader;
|
||||
public Quad ScreenSpaceDrawQuad;
|
||||
public Vector2 FlashlightPosition;
|
||||
public Vector2 FlashlightSize;
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override string Description => "Less zoom...";
|
||||
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)
|
||||
{
|
||||
|
64
osu.Game/Rulesets/Mods/ModTimeRamp.cs
Normal file
64
osu.Game/Rulesets/Mods/ModTimeRamp.cs
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
19
osu.Game/Rulesets/Mods/ModWindDown.cs
Normal file
19
osu.Game/Rulesets/Mods/ModWindDown.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.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;
|
||||
}
|
||||
}
|
19
osu.Game/Rulesets/Mods/ModWindUp.cs
Normal file
19
osu.Game/Rulesets/Mods/ModWindUp.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.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;
|
||||
}
|
||||
}
|
@ -1,14 +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.Game.Rulesets.Objects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
public abstract class AutoGenerator<T> : IAutoGenerator
|
||||
where T : HitObject
|
||||
public abstract class AutoGenerator : IAutoGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the auto replay and returns it.
|
||||
@ -21,11 +19,11 @@ namespace osu.Game.Rulesets.Replays
|
||||
/// <summary>
|
||||
/// The beatmap we're making.
|
||||
/// </summary>
|
||||
protected Beatmap<T> Beatmap;
|
||||
protected IBeatmap Beatmap;
|
||||
|
||||
#endregion
|
||||
|
||||
protected AutoGenerator(Beatmap<T> beatmap)
|
||||
protected AutoGenerator(IBeatmap beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets
|
||||
/// <returns>An enumerable of constructed <see cref="Mod"/>s</returns>
|
||||
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)
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
@ -63,6 +64,10 @@ namespace osu.Game.Rulesets.UI
|
||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
this.beatmap = beatmap.Value;
|
||||
|
||||
Cursor = CreateCursor();
|
||||
if (Cursor != null)
|
||||
CursorTargetContainer.Add(Cursor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -82,6 +87,23 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <param name="h">The DrawableHitObject to remove.</param>
|
||||
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>
|
||||
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
|
||||
/// This does not add the <see cref="Playfield"/> to the draw hierarchy.
|
||||
|
@ -16,7 +16,9 @@ using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Replays;
|
||||
@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.UI
|
||||
/// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class RulesetContainer : Container
|
||||
public abstract class RulesetContainer : Container, IProvideCursor
|
||||
{
|
||||
/// <summary>
|
||||
/// The selected variant.
|
||||
@ -74,10 +76,11 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
public Container Overlays { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The cursor provided by this <see cref="RulesetContainer"/>. May be null if no cursor is provided.
|
||||
/// </summary>
|
||||
public readonly CursorContainer Cursor;
|
||||
public CursorContainer Cursor => Playfield.Cursor;
|
||||
|
||||
public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value;
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor
|
||||
|
||||
public readonly Ruleset Ruleset;
|
||||
|
||||
@ -101,8 +104,6 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
KeyBindingInputManager.UseParentInput = !paused.NewValue;
|
||||
};
|
||||
|
||||
Cursor = CreateCursor();
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -259,9 +260,6 @@ namespace osu.Game.Rulesets.UI
|
||||
Playfield
|
||||
});
|
||||
|
||||
if (Cursor != null)
|
||||
KeyBindingInputManager.Add(Cursor);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
KeyBindingInputManager,
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Screens
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
@ -106,7 +106,7 @@ namespace osu.Game.Screens
|
||||
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()
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private readonly float[] frequencyAmplitudes = new float[256];
|
||||
|
||||
private Shader shader;
|
||||
private IShader shader;
|
||||
private readonly Texture texture;
|
||||
|
||||
public LogoVisualisation()
|
||||
@ -131,8 +131,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new VisualisationDrawNode();
|
||||
|
||||
private readonly VisualiserSharedData sharedData = new VisualiserSharedData();
|
||||
|
||||
protected override void ApplyDrawNode(DrawNode node)
|
||||
{
|
||||
base.ApplyDrawNode(node);
|
||||
@ -142,29 +140,23 @@ namespace osu.Game.Screens.Menu
|
||||
visNode.Shader = shader;
|
||||
visNode.Texture = texture;
|
||||
visNode.Size = DrawSize.X;
|
||||
visNode.Shared = sharedData;
|
||||
visNode.Colour = AccentColour;
|
||||
visNode.AudioData = frequencyAmplitudes;
|
||||
}
|
||||
|
||||
private class VisualiserSharedData
|
||||
{
|
||||
public readonly QuadBatch<TexturedVertex2D> VertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
|
||||
}
|
||||
|
||||
private class VisualisationDrawNode : DrawNode
|
||||
{
|
||||
public Shader Shader;
|
||||
public IShader Shader;
|
||||
public Texture Texture;
|
||||
|
||||
public VisualiserSharedData Shared;
|
||||
|
||||
//Asuming the logo is a circle, we don't need a second dimension.
|
||||
public float Size;
|
||||
|
||||
public Color4 Colour;
|
||||
public float[] AudioData;
|
||||
|
||||
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
|
||||
|
||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||
{
|
||||
base.Draw(vertexAction);
|
||||
@ -209,7 +201,7 @@ namespace osu.Game.Screens.Menu
|
||||
rectangle,
|
||||
colourInfo,
|
||||
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.
|
||||
Vector2.Divide(inflation, barSize.Yx));
|
||||
}
|
||||
@ -218,6 +210,13 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
Shader.Unbind();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBatch.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Multi
|
||||
@ -19,7 +17,7 @@ namespace osu.Game.Screens.Multi
|
||||
{
|
||||
public const float HEIGHT = 121;
|
||||
|
||||
private readonly OsuSpriteText screenType;
|
||||
private readonly ScreenTitle title;
|
||||
private readonly HeaderBreadcrumbControl breadcrumbs;
|
||||
|
||||
public Header(ScreenStack stack)
|
||||
@ -40,39 +38,12 @@ namespace osu.Game.Screens.Multi
|
||||
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
title = new ScreenTitle
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Position = new Vector2(-35f, 5f),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Size = new Vector2(25),
|
||||
Icon = FontAwesome.fa_osu_multi,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "multiplayer ",
|
||||
Font = OsuFont.GetFont(size: 25)
|
||||
},
|
||||
screenType = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 25)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Icon = FontAwesome.fa_osu_multi,
|
||||
Title = "multiplayer ",
|
||||
},
|
||||
breadcrumbs = new HeaderBreadcrumbControl(stack)
|
||||
{
|
||||
@ -87,7 +58,7 @@ namespace osu.Game.Screens.Multi
|
||||
breadcrumbs.Current.ValueChanged += scren =>
|
||||
{
|
||||
if (scren.NewValue is IMultiplayerSubScreen multiScreen)
|
||||
screenType.Text = multiScreen.ShortTitle.ToLowerInvariant();
|
||||
title.Page = multiScreen.ShortTitle.ToLowerInvariant();
|
||||
};
|
||||
|
||||
breadcrumbs.Current.TriggerChange();
|
||||
@ -96,7 +67,7 @@ namespace osu.Game.Screens.Multi
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
screenType.Colour = colours.Yellow;
|
||||
title.AccentColour = colours.Yellow;
|
||||
breadcrumbs.StripColour = colours.Green;
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,14 @@ namespace osu.Game.Screens.Multi.Lounge
|
||||
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)
|
||||
{
|
||||
processingOverlay.Show();
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Multi.Components;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
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();
|
||||
}
|
||||
|
@ -28,13 +28,15 @@ namespace osu.Game.Screens.Multi.Match.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
roomId.BindValueChanged(_ => updateChannel(), true);
|
||||
channelId.BindValueChanged(_ => updateChannel(), true);
|
||||
}
|
||||
|
||||
private void updateChannel()
|
||||
{
|
||||
if (roomId.Value != null)
|
||||
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#mp_{roomId.Value}" });
|
||||
if (roomId.Value == null || channelId.Value == 0)
|
||||
return;
|
||||
|
||||
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
151
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal file
151
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
public readonly PlaybackSettings PlaybackSettings;
|
||||
|
||||
public readonly VisualSettings VisualSettings;
|
||||
|
||||
//public readonly CollectionSettings CollectionSettings;
|
||||
|
||||
//public readonly DiscussionSettings DiscussionSettings;
|
||||
|
||||
public PlayerSettingsOverlay()
|
||||
|
@ -1,12 +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 System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -40,7 +40,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
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;
|
||||
|
||||
@ -92,11 +94,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
Progress.Objects = rulesetContainer.Objects;
|
||||
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
|
||||
Progress.RequestSeek = pos => adjustableClock.Seek(pos);
|
||||
Progress.RequestSeek = time => RequestSeek(time);
|
||||
|
||||
ModDisplay.Current.BindTo(working.Mods);
|
||||
|
||||
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
|
@ -7,16 +7,13 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which handles pausing children, displaying a pause overlay with choices and processing the clock.
|
||||
/// Exposes a <see cref="GameplayClock"/> to children via DI.
|
||||
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
|
||||
/// A container which handles pausing children, displaying an overlay blocking its children during paused state.
|
||||
/// </summary>
|
||||
public class PausableGameplayContainer : Container
|
||||
{
|
||||
@ -44,46 +41,33 @@ namespace osu.Game.Screens.Play
|
||||
public Action OnRetry;
|
||||
public Action OnQuit;
|
||||
|
||||
private readonly FramedClock offsetClock;
|
||||
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
|
||||
|
||||
/// <summary>
|
||||
/// The final clock which is exposed to underlying components.
|
||||
/// </summary>
|
||||
[Cached]
|
||||
private readonly GameplayClock gameplayClock;
|
||||
public Action Stop;
|
||||
public Action Start;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PausableGameplayContainer"/>.
|
||||
/// </summary>
|
||||
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
|
||||
/// <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)
|
||||
public PausableGameplayContainer()
|
||||
{
|
||||
this.offsetClock = offsetClock;
|
||||
this.adjustableClock = adjustableClock;
|
||||
|
||||
gameplayClock = new GameplayClock(offsetClock);
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddInternal(content = new Container
|
||||
InternalChildren = new[]
|
||||
{
|
||||
Clock = this.offsetClock,
|
||||
ProcessCustomClock = false,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
});
|
||||
|
||||
AddInternal(pauseOverlay = new PauseOverlay
|
||||
{
|
||||
OnResume = () =>
|
||||
content = new Container
|
||||
{
|
||||
IsResuming = true;
|
||||
this.Delay(400).Schedule(Resume);
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
OnRetry = () => OnRetry(),
|
||||
OnQuit = () => OnQuit(),
|
||||
});
|
||||
pauseOverlay = new PauseOverlay
|
||||
{
|
||||
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.
|
||||
@ -93,7 +77,7 @@ namespace osu.Game.Screens.Play
|
||||
if (IsPaused.Value) return;
|
||||
|
||||
// stop the seekable clock (stops the audio eventually)
|
||||
adjustableClock.Stop();
|
||||
Stop?.Invoke();
|
||||
IsPaused.Value = true;
|
||||
|
||||
pauseOverlay.Show();
|
||||
@ -105,14 +89,12 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (!IsPaused.Value) return;
|
||||
|
||||
IsPaused.Value = false;
|
||||
IsResuming = false;
|
||||
lastPauseActionTime = Time.Current;
|
||||
|
||||
// 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();
|
||||
IsPaused.Value = false;
|
||||
|
||||
Start?.Invoke();
|
||||
|
||||
pauseOverlay.Hide();
|
||||
}
|
||||
@ -131,9 +113,6 @@ namespace osu.Game.Screens.Play
|
||||
if (!game.IsActive.Value && CanPause)
|
||||
Pause();
|
||||
|
||||
if (!IsPaused.Value)
|
||||
offsetClock.ProcessFrame();
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
|
@ -3,24 +3,19 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
@ -34,7 +29,7 @@ using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class Player : ScreenWithBeatmapBackground, IProvideCursor
|
||||
public class Player : ScreenWithBeatmapBackground
|
||||
{
|
||||
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||
|
||||
@ -53,22 +48,11 @@ namespace osu.Game.Screens.Play
|
||||
public bool AllowResults { get; set; } = true;
|
||||
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
private Bindable<double> userAudioOffset;
|
||||
|
||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||
|
||||
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]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
@ -98,25 +82,113 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
|
||||
|
||||
private GameplayClockContainer gameplayClockContainer;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
|
||||
{
|
||||
this.api = api;
|
||||
|
||||
WorkingBeatmap working = Beatmap.Value;
|
||||
if (working is DummyWorkingBeatmap)
|
||||
WorkingBeatmap working = loadBeatmap();
|
||||
|
||||
if (working == null)
|
||||
return;
|
||||
|
||||
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
|
||||
|
||||
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
|
||||
{
|
||||
beatmap = working.Beatmap;
|
||||
var beatmap = working.Beatmap;
|
||||
|
||||
if (beatmap == null)
|
||||
throw new InvalidOperationException("Beatmap was not loaded");
|
||||
@ -140,119 +212,17 @@ namespace osu.Game.Screens.Play
|
||||
if (!RulesetContainer.Objects.Any())
|
||||
{
|
||||
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Could not load beatmap sucessfully!");
|
||||
//couldn't load, hard abort!
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
||||
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);
|
||||
return working;
|
||||
}
|
||||
|
||||
private void performUserRequestedExit()
|
||||
@ -321,7 +291,7 @@ namespace osu.Game.Screens.Play
|
||||
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
||||
return false;
|
||||
|
||||
adjustableClock.Stop();
|
||||
gameplayClockContainer.Stop();
|
||||
|
||||
HasFailed = true;
|
||||
failOverlay.Retries = RestartCount;
|
||||
@ -355,24 +325,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
sourceClock.Reset();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
adjustableClock.ChangeSource(sourceClock);
|
||||
applyRateFromMods();
|
||||
|
||||
this.Delay(750).Schedule(() =>
|
||||
{
|
||||
if (!PausableGameplayContainer.IsPaused.Value)
|
||||
{
|
||||
adjustableClock.Start();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
gameplayClockContainer.Restart();
|
||||
|
||||
PausableGameplayContainer.Alpha = 0;
|
||||
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))
|
||||
{
|
||||
// In the case of replays, we may have changed the playback rate.
|
||||
applyRateFromMods();
|
||||
gameplayClockContainer.ResetLocalAdjustments();
|
||||
|
||||
fadeOut();
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
@ -16,7 +15,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
|
||||
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;
|
||||
|
||||
@ -47,31 +52,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
}
|
||||
},
|
||||
},
|
||||
rateSlider = new PlayerSliderBar<double>
|
||||
{
|
||||
Bindable = new BindableDouble(1)
|
||||
{
|
||||
Default = 1,
|
||||
MinValue = 0.5,
|
||||
MaxValue = 2,
|
||||
Precision = 0.1,
|
||||
},
|
||||
}
|
||||
rateSlider = new PlayerSliderBar<double> { Bindable = UserPlaybackRate }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void 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);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -60,9 +61,12 @@ namespace osu.Game.Screens.Select
|
||||
if (base.OnExiting(next))
|
||||
return true;
|
||||
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value?.Beatmap);
|
||||
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value?.RequiredMods;
|
||||
Ruleset.Value = CurrentItem.Value?.Ruleset;
|
||||
if (CurrentItem.Value != null)
|
||||
{
|
||||
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;
|
||||
Ruleset.Disabled = true;
|
||||
|
@ -20,14 +20,14 @@ namespace osu.Game.Utils
|
||||
|
||||
private readonly List<Task> tasks = new List<Task>();
|
||||
|
||||
private Exception lastException;
|
||||
|
||||
public RavenLogger(OsuGame game)
|
||||
{
|
||||
raven.Release = game.Version;
|
||||
|
||||
if (!game.IsDeployedBuild) return;
|
||||
|
||||
Exception lastException = null;
|
||||
|
||||
Logger.NewEntry += entry =>
|
||||
{
|
||||
if (entry.Level < LogLevel.Verbose) return;
|
||||
|
@ -16,7 +16,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<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="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
@ -105,12 +105,12 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2019.301.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.301.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2019.308.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.308.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.22.0" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.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>
|
||||
</Project>
|
||||
|
@ -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/=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/=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/=MD/@EntryIndexedValue">MD5</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String>
|
||||
|
Loading…
Reference in New Issue
Block a user