diff --git a/osu-resources b/osu-resources index f85c594c18..2d8a6c1699 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit f85c594c182db2b01233e29ca52639b7baa00402 +Subproject commit 2d8a6c1699ff1acd3915fc28e8906dabf1b145a3 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs new file mode 100644 index 0000000000..edd9c74485 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -0,0 +1,212 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens.Testing; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseTaikoHitObjects : TestCase + { + public override string Description => "Taiko hit objects"; + + private bool kiai; + + public override void Reset() + { + base.Reset(); + + AddToggle("Kiai", b => + { + kiai = !kiai; + Reset(); + }); + + Add(new CentreHitCircle(new CirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(100, 100) + }); + + Add(new CentreHitCircle(new AccentedCirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(350, 100) + }); + + Add(new RimHitCircle(new CirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(100, 300) + }); + + Add(new RimHitCircle(new AccentedCirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(350, 300) + }); + + Add(new SwellCircle(new CirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(100, 500) + }); + + Add(new SwellCircle(new AccentedCirclePiece() + { + KiaiMode = kiai + }) + { + Position = new Vector2(350, 500) + }); + + Add(new DrumRollCircle(new CirclePiece() + { + KiaiMode = kiai + }) + { + Width = 250, + Position = new Vector2(575, 100) + }); + + Add(new DrumRollCircle(new AccentedCirclePiece() + { + KiaiMode = kiai + }) + { + Width = 250, + Position = new Vector2(575, 300) + }); + } + + private class SwellCircle : BaseCircle + { + public SwellCircle(CirclePiece piece) + : base(piece) + { + Piece.Add(new TextAwesome + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = SYMBOL_INNER_SIZE, + Icon = FontAwesome.fa_asterisk, + Shadow = false + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Piece.AccentColour = colours.YellowDark; + } + } + + private class DrumRollCircle : BaseCircle + { + public DrumRollCircle(CirclePiece piece) + : base(piece) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Piece.AccentColour = colours.YellowDark; + } + } + + private class CentreHitCircle : BaseCircle + { + public CentreHitCircle(CirclePiece piece) + : base(piece) + { + Piece.Add(new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(SYMBOL_INNER_SIZE), + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Piece.AccentColour = colours.PinkDarker; + } + } + + private class RimHitCircle : BaseCircle + { + public RimHitCircle(CirclePiece piece) + : base(piece) + { + Piece.Add(new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(SYMBOL_SIZE), + BorderThickness = SYMBOL_BORDER, + BorderColour = Color4.White, + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Piece.AccentColour = colours.BlueDarker; + } + } + + private abstract class BaseCircle : Container + { + protected const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; + protected const float SYMBOL_BORDER = 8; + protected const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; + + protected readonly CirclePiece Piece; + + protected BaseCircle(CirclePiece piece) + { + Piece = piece; + + Add(Piece); + } + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index e63306300d..f1b4a99510 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -194,6 +194,7 @@ + diff --git a/osu.Game.Modes.Osu/OsuAutoReplay.cs b/osu.Game.Modes.Osu/OsuAutoReplay.cs index 61b7466d86..b3fece2b84 100644 --- a/osu.Game.Modes.Osu/OsuAutoReplay.cs +++ b/osu.Game.Modes.Osu/OsuAutoReplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Modes.Osu createAutoReplay(); } - internal class LegacyReplayFrameComparer : IComparer + private class LegacyReplayFrameComparer : IComparer { public int Compare(LegacyReplayFrame f1, LegacyReplayFrame f2) { diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs index c77c7762e3..e165f40442 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; namespace osu.Game.Modes.Taiko.Objects.Drawable { @@ -16,6 +17,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Origin = Anchor.Centre; RelativePositionAxes = Axes.X; + + Children = new[] + { + CreateCircle() + }; } protected override void LoadComplete() @@ -42,5 +48,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { UpdateScrollPosition(Time.Current); } + + protected abstract CirclePiece CreateCircle(); } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/AccentedCirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/AccentedCirclePiece.cs new file mode 100644 index 0000000000..c02cbc572a --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/AccentedCirclePiece.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +{ + /// + /// A type of circle piece which is drawn at a higher scale as an "accent". + /// + public class AccentedCirclePiece : CirclePiece + { + /// + /// The amount to scale up the base circle to show it as an "accented" piece. + /// + private const float accent_scale = 1.5f; + + public AccentedCirclePiece() + { + SymbolContainer.Scale = new Vector2(accent_scale); + } + + public override Vector2 Size => new Vector2(base.Size.X, base.Size.Y * accent_scale); + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs new file mode 100644 index 0000000000..453ab7a05d --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs @@ -0,0 +1,156 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Backgrounds; +using OpenTK.Graphics; +using System; + +namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +{ + /// + /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. + /// + /// The body of this piece will overshoot its parent by to form + /// a rounded (_[-Width-]_) figure such that a regular "circle" is the result of a parent with Width = 0. + /// + /// + public class CirclePiece : Container + { + private Color4 accentColour; + /// + /// The colour of the inner circle and outer glows. + /// + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + + innerBackground.Colour = AccentColour; + + triangles.ColourLight = AccentColour; + triangles.ColourDark = AccentColour.Darken(0.1f); + + resetEdgeEffects(); + } + } + + private bool kiaiMode; + /// + /// Whether Kiai mode effects are enabled for this circle piece. + /// + public bool KiaiMode + { + get { return kiaiMode; } + set + { + kiaiMode = value; + + resetEdgeEffects(); + } + } + + public override Anchor Origin + { + get { return Anchor.CentreLeft; } + set { throw new InvalidOperationException($"{nameof(CirclePiece)} must always use CentreLeft origin."); } + } + + protected override Container Content => SymbolContainer; + protected readonly Container SymbolContainer; + + private readonly Container innerLayer; + private readonly Container innerCircleContainer; + private readonly Box innerBackground; + private readonly Triangles triangles; + + public CirclePiece() + { + RelativeSizeAxes = Axes.X; + Height = TaikoHitObject.CIRCLE_RADIUS * 2; + + // The "inner layer" is the body of the CirclePiece that overshoots it by Height/2 px on both sides + AddInternal(innerLayer = new Container + { + Name = "Inner Layer", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Framework.Graphics.Drawable[] + { + innerCircleContainer = new CircularContainer + { + Name = "Inner Circle", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Framework.Graphics.Drawable[] + { + innerBackground = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + triangles = new Triangles + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + } + }, + new CircularContainer + { + Name = "Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + BorderThickness = 8, + BorderColour = Color4.White, + Masking = true, + Children = new[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + SymbolContainer = new Container + { + Name = "Symbol", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }); + } + + protected override void Update() + { + // Add the overshoot to compensate for corner radius + innerLayer.Width = DrawWidth + DrawHeight; + } + + private void resetEdgeEffects() + { + innerCircleContainer.EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Colour = AccentColour, + Radius = KiaiMode ? 50 : 8 + }; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index a455167c6b..b1f47832c0 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -53,11 +53,13 @@ + + diff --git a/osu.Game/Database/ScoreDatabase.cs b/osu.Game/Database/ScoreDatabase.cs index 665ae8649e..096c0dcc29 100644 --- a/osu.Game/Database/ScoreDatabase.cs +++ b/osu.Game/Database/ScoreDatabase.cs @@ -101,7 +101,7 @@ namespace osu.Game.Database using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) using (var reader = new StreamReader(lzma)) - score.Replay = new LegacyReplay(reader); + score.Replay = score.CreateLegacyReplayFrom(reader); } } diff --git a/osu.Game/Modes/LegacyReplay.cs b/osu.Game/Modes/LegacyReplay.cs index 4b77550cbc..d57d4a15d2 100644 --- a/osu.Game/Modes/LegacyReplay.cs +++ b/osu.Game/Modes/LegacyReplay.cs @@ -46,13 +46,13 @@ namespace osu.Game.Modes } } - public override ReplayInputHandler GetInputHandler() => new LegacyReplayInputHandler(Frames); + public override ReplayInputHandler CreateInputHandler() => new LegacyReplayInputHandler(Frames); /// /// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. /// It handles logic of any frames which *must* be executed. /// - public class LegacyReplayInputHandler : ReplayInputHandler + protected class LegacyReplayInputHandler : ReplayInputHandler { private readonly List replayContent; @@ -163,7 +163,7 @@ namespace osu.Game.Modes return currentTime = time; } - private class ReplayMouseState : MouseState + protected class ReplayMouseState : MouseState { public ReplayMouseState(Vector2 position, IEnumerable list) { @@ -172,7 +172,7 @@ namespace osu.Game.Modes } } - private class ReplayKeyboardState : KeyboardState + protected class ReplayKeyboardState : KeyboardState { public ReplayKeyboardState(List keys) { @@ -182,7 +182,7 @@ namespace osu.Game.Modes } [Flags] - public enum LegacyButtonState + protected enum LegacyButtonState { None = 0, Left1 = 1, @@ -192,7 +192,7 @@ namespace osu.Game.Modes Smoke = 16 } - public class LegacyReplayFrame + protected class LegacyReplayFrame { public Vector2 Position => new Vector2(MouseX, MouseY); diff --git a/osu.Game/Modes/Mods/Mod.cs b/osu.Game/Modes/Mods/Mod.cs index 8416ae6211..c53c6faa21 100644 --- a/osu.Game/Modes/Mods/Mod.cs +++ b/osu.Game/Modes/Mods/Mod.cs @@ -157,7 +157,7 @@ namespace osu.Game.Modes.Mods public void Apply(HitRenderer hitRenderer) { - hitRenderer.InputManager.ReplayInputHandler = CreateReplayScore(hitRenderer.Beatmap)?.Replay?.GetInputHandler(); + hitRenderer.InputManager.ReplayInputHandler = CreateReplayScore(hitRenderer.Beatmap)?.Replay?.CreateInputHandler(); } } diff --git a/osu.Game/Modes/Replay.cs b/osu.Game/Modes/Replay.cs index 0a41a12335..6d93afdeb9 100644 --- a/osu.Game/Modes/Replay.cs +++ b/osu.Game/Modes/Replay.cs @@ -7,6 +7,6 @@ namespace osu.Game.Modes { public abstract class Replay { - public virtual ReplayInputHandler GetInputHandler() => null; + public virtual ReplayInputHandler CreateInputHandler() => null; } } \ No newline at end of file diff --git a/osu.Game/Modes/Scoring/Score.cs b/osu.Game/Modes/Scoring/Score.cs index fa74fba279..75c243278d 100644 --- a/osu.Game/Modes/Scoring/Score.cs +++ b/osu.Game/Modes/Scoring/Score.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using osu.Game.Database; using osu.Game.Modes.Mods; using osu.Game.Users; +using System.IO; namespace osu.Game.Modes.Scoring { @@ -43,6 +44,13 @@ namespace osu.Game.Modes.Scoring [JsonProperty(@"date")] public DateTime Date; + /// + /// Creates a legacy replay which is read from a stream. + /// + /// The stream reader. + /// The replay. + public virtual Replay CreateLegacyReplayFrom(StreamReader reader) => new LegacyReplay(reader); + // [JsonProperty(@"count50")] 0, //[JsonProperty(@"count100")] 0, //[JsonProperty(@"count300")] 100, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8ac809e591..e6abdab729 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,7 +126,7 @@ namespace osu.Game Beatmap.Value = BeatmapDatabase.GetWorkingBeatmap(s.Beatmap); - menu.Push(new PlayerLoader(new Player { ReplayInputHandler = s.Replay.GetInputHandler() })); + menu.Push(new PlayerLoader(new Player { ReplayInputHandler = s.Replay.CreateInputHandler() })); } protected override void LoadComplete() diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 989e605c16..32dd814fdc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -160,7 +160,7 @@ - + @@ -320,7 +320,7 @@ - + @@ -396,4 +396,4 @@ --> - \ No newline at end of file +