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

Merge branch 'master' into clock-fixes

This commit is contained in:
Dean Herbert 2018-03-05 17:45:08 +09:00 committed by GitHub
commit 2003887cc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 578 additions and 390 deletions

View File

@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
@ -101,6 +103,8 @@ namespace osu.Game.Rulesets.Catch
public override int LegacyID => 2; public override int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
public CatchRuleset(RulesetInfo rulesetInfo = null) public CatchRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Replays
} }
else if (h.HyperDash) else if (h.HyperDash)
{ {
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition, ReplayButtonState.Right1)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
else if (dashRequired) else if (dashRequired)
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Replays
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement //dash movement
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, ReplayButtonState.Left1)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{ {
double timeBefore = positionChange / movement_speed; double timeBefore = positionChange / movement_speed;
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition, ReplayButtonState.Right1)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }

View File

@ -3,37 +3,51 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
public class CatchFramedReplayInputHandler : FramedReplayInputHandler public class CatchFramedReplayInputHandler : FramedReplayInputHandler<CatchReplayFrame>
{ {
public CatchFramedReplayInputHandler(Replay replay) public CatchFramedReplayInputHandler(Replay replay)
: base(replay) : base(replay)
{ {
} }
protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
protected float? Position
{
get
{
if (!HasFrames)
return null;
return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
}
}
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates()
{ {
if (!Position.HasValue) return new List<InputState>(); if (!Position.HasValue) return new List<InputState>();
var action = new List<CatchAction>(); var actions = new List<CatchAction>();
if (CurrentFrame.ButtonState == ReplayButtonState.Left1) if (CurrentFrame.Dashing)
action.Add(CatchAction.Dash); actions.Add(CatchAction.Dash);
if (Position.Value.X > CurrentFrame.Position.X) if (Position.Value > CurrentFrame.Position)
action.Add(CatchAction.MoveRight); actions.Add(CatchAction.MoveRight);
else if (Position.Value.X < CurrentFrame.Position.X) else if (Position.Value < CurrentFrame.Position)
action.Add(CatchAction.MoveLeft); actions.Add(CatchAction.MoveLeft);
return new List<InputState> return new List<InputState>
{ {
new CatchReplayState new CatchReplayState
{ {
PressedActions = action, PressedActions = actions,
CatcherX = Position.Value.X CatcherX = Position.Value
}, },
}; };
} }

View File

@ -1,17 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
public class CatchReplayFrame : ReplayFrame public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{ {
public override bool IsImportant => MouseX > 0; public float Position;
public bool Dashing;
public CatchReplayFrame(double time, float? x = null, ReplayButtonState button = ReplayButtonState.None) public CatchReplayFrame()
: base(time, x ?? -1, null, button)
{ {
} }
public CatchReplayFrame(double time, float? position = null, bool dashing = false)
: base(time)
{
Position = position ?? -1;
Dashing = dashing;
}
public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
{
Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
}
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor(); protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor();

View File

@ -14,5 +14,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The number of <see cref="Column"/>s which this stage contains. /// The number of <see cref="Column"/>s which this stage contains.
/// </summary> /// </summary>
public int Columns; public int Columns;
/// <summary>
/// Whether the column index is a special column for this stage.
/// </summary>
/// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns>
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
} }
} }

View File

@ -12,6 +12,8 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Mania
public override int LegacyID => 3; public override int LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public ManiaRuleset(RulesetInfo rulesetInfo = null) public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
return new Score return new Score
{ {
User = new User { Username = "osu!topus!" }, User = new User { Username = "osu!topus!" },
Replay = new ManiaAutoGenerator(beatmap).Generate(), Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
}; };
} }
} }

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
@ -15,10 +15,31 @@ namespace osu.Game.Rulesets.Mania.Replays
{ {
public const double RELEASE_DELAY = 20; public const double RELEASE_DELAY = 20;
public ManiaAutoGenerator(Beatmap<ManiaHitObject> beatmap) public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
private readonly ManiaAction[] columnActions;
public ManiaAutoGenerator(ManiaBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
Replay = new Replay { User = new User { Username = @"Autoplay" } }; Replay = new Replay { User = new User { Username = @"Autoplay" } };
columnActions = new ManiaAction[Beatmap.TotalColumns];
var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1;
int totalCounter = 0;
foreach (var stage in Beatmap.Stages)
{
for (int i = 0; i < stage.Columns; i++)
{
if (stage.IsSpecialColumn(i))
columnActions[totalCounter] = specialAction++;
else
columnActions[totalCounter] = normalAction++;
totalCounter++;
}
}
} }
protected Replay Replay; protected Replay Replay;
@ -30,18 +51,18 @@ namespace osu.Game.Rulesets.Mania.Replays
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
int activeColumns = 0; var actions = new List<ManiaAction>();
foreach (var group in pointGroups) foreach (var group in pointGroups)
{ {
foreach (var point in group) foreach (var point in group)
{ {
if (point is HitPoint) if (point is HitPoint)
activeColumns |= 1 << point.Column; actions.Add(columnActions[point.Column]);
if (point is ReleasePoint) if (point is ReleasePoint)
activeColumns ^= 1 << point.Column; actions.Remove(columnActions[point.Column]);
} }
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns)); Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
} }
return Replay; return Replay;

View File

@ -4,40 +4,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays namespace osu.Game.Rulesets.Mania.Replays
{ {
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler<ManiaReplayFrame>
{ {
private readonly ManiaRulesetContainer container; public ManiaFramedReplayInputHandler(Replay replay)
public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
: base(replay) : base(replay)
{ {
this.container = container;
} }
private ManiaPlayfield playfield; protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
public override List<InputState> GetPendingStates()
{
var actions = new List<ManiaAction>();
if (playfield == null) public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
playfield = (ManiaPlayfield)container.Playfield;
int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
int counter = 0;
while (activeColumns > 0)
{
if ((activeColumns & 1) > 0)
actions.Add(playfield.Columns.ElementAt(counter).Action);
counter++;
activeColumns >>= 1;
}
return new List<InputState> { new ReplayState<ManiaAction> { PressedActions = actions } };
}
} }
} }

View File

@ -1,17 +1,59 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Mania.Replays namespace osu.Game.Rulesets.Mania.Replays
{ {
public class ManiaReplayFrame : ReplayFrame public class ManiaReplayFrame : ReplayFrame, IConvertibleReplayFrame
{ {
public override bool IsImportant => MouseX > 0; public List<ManiaAction> Actions = new List<ManiaAction>();
public ManiaReplayFrame(double time, int activeColumns) public ManiaReplayFrame()
: base(time, activeColumns, null, ReplayButtonState.None)
{ {
} }
public ManiaReplayFrame(double time, params ManiaAction[] actions)
: base(time)
{
Actions.AddRange(actions);
}
public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
{
// We don't need to fully convert, just create the converter
var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap);
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
var stage = new StageDefinition { Columns = converter.TargetColumns };
var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1;
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
int counter = 0;
while (activeColumns > 0)
{
var isSpecial = stage.IsSpecialColumn(counter);
if ((activeColumns & 1) > 0)
Actions.Add(isSpecial ? specialAction : normalAction);
if (isSpecial)
specialAction++;
else
normalAction++;
counter++;
activeColumns >>= 1;
}
}
} }
} }

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | // | - |
// | | // | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
@ -27,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
} }
[Test] [Test]
@ -40,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | // | * |
// | | // | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
@ -48,8 +50,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
} }
[Test] [Test]
@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | - | // | - | - |
// | | | // | | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
@ -68,8 +70,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
} }
[Test] [Test]
@ -81,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | * | // | * | * |
// | | | // | | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
@ -90,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
} }
[Test] [Test]
@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | | // | - | |
// | | | // | | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
@ -113,10 +115,10 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released"); Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
} }
[Test] [Test]
@ -129,7 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | | // | * | |
// | | | // | | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
@ -140,10 +142,11 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released"); Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
} }
[Test] [Test]
@ -155,7 +158,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | | // | * | |
// | | | // | | |
var beatmap = new Beatmap<ManiaHitObject>(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
@ -165,9 +168,12 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time"); Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.AreEqual(2, generated.Frames[2].MouseX, "Key 1 has not been released or key 2 has not been pressed"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released"); Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
} }
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
@ -103,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant); protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
} }

View File

@ -48,13 +48,11 @@ namespace osu.Game.Rulesets.Mania.UI
private Color4 specialColumnColour; private Color4 specialColumnColour;
private readonly int firstColumnIndex; private readonly int firstColumnIndex;
private readonly StageDefinition definition;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(ScrollingDirection.Up) : base(ScrollingDirection.Up)
{ {
this.firstColumnIndex = firstColumnIndex; this.firstColumnIndex = firstColumnIndex;
this.definition = definition;
Name = "Stage"; Name = "Stage";
@ -131,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var isSpecial = isSpecialColumn(i); var isSpecial = definition.IsSpecialColumn(i);
var column = new Column var column = new Column
{ {
IsSpecial = isSpecial, IsSpecial = isSpecial,
@ -160,13 +158,6 @@ namespace osu.Game.Rulesets.Mania.UI
AddNested(c); AddNested(c);
} }
/// <summary>
/// Whether the column index is a special column for this playfield.
/// </summary>
/// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns>
private bool isSpecialColumn(int column) => definition.Columns % 2 == 1 && column == definition.Columns / 2;
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
var maniaObject = (ManiaHitObject)h.HitObject; var maniaObject = (ManiaHitObject)h.HitObject;

View File

@ -19,6 +19,8 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
@ -145,6 +147,8 @@ namespace osu.Game.Rulesets.Osu
public override int LegacyID => 0; public override int LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public OsuRuleset(RulesetInfo rulesetInfo = null) public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -6,7 +6,7 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using System; using System;
using System.Diagnostics; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
@ -64,9 +64,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
buttonIndex = 0; buttonIndex = 0;
AddFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None)); AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None)); AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None)); AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
@ -91,18 +91,18 @@ namespace osu.Game.Rulesets.Osu.Replays
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot). // Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{ {
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None)); if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None)); if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
} }
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{ {
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None)); if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None)); if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
} }
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{ {
if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None)); if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None)); if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
} }
} }
@ -118,9 +118,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction? // TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner) if (h is Spinner)
{ {
calcSpinnerStartPosAndDirection(Frames[Frames.Count - 1].Position, out startPosition, out spinnerDirection); calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
Vector2 spinCentreOffset = SPINNER_CENTRE - Frames[Frames.Count - 1].Position; Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
if (spinCentreOffset.Length > SPIN_RADIUS) if (spinCentreOffset.Length > SPIN_RADIUS)
{ {
@ -192,13 +192,13 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{ {
ReplayFrame lastFrame = Frames[Frames.Count - 1]; OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
// Wait until Auto could "see and react" to the next note. // Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
if (waitTime > lastFrame.Time) if (waitTime > lastFrame.Time)
{ {
lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState); lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions };
AddFrameToReplay(lastFrame); AddFrameToReplay(lastFrame);
} }
@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Replays
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
{ {
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState)); AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
} }
buttonIndex = 0; buttonIndex = 0;
@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
// Time to insert the first frame which clicks the object // Time to insert the first frame which clicks the object
// Here we mainly need to determine which button to use // Here we mainly need to determine which button to use
ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1; var action = buttonIndex % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton;
ReplayFrame startFrame = new ReplayFrame(h.StartTime, startPosition.X, startPosition.Y, button); var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY; double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
int endDelay = h is Spinner ? 1 : 0; int endDelay = h is Spinner ? 1 : 0;
ReplayFrame endFrame = new ReplayFrame(hEndTime + endDelay, h.StackedEndPosition.X, h.StackedEndPosition.Y, ReplayButtonState.None); var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
// Decrement because we want the previous frame, not the next one // Decrement because we want the previous frame, not the next one
int index = FindInsertionIndex(startFrame) - 1; int index = FindInsertionIndex(startFrame) - 1;
@ -248,19 +248,18 @@ namespace osu.Game.Rulesets.Osu.Replays
// Do we have a previous frame? No need to check for < replay.Count since we decremented! // Do we have a previous frame? No need to check for < replay.Count since we decremented!
if (index >= 0) if (index >= 0)
{ {
ReplayFrame previousFrame = Frames[index]; var previousFrame = (OsuReplayFrame)Frames[index];
var previousButton = previousFrame.ButtonState; var previousActions = previousFrame.Actions;
// If a button is already held, then we simply alternate // If a button is already held, then we simply alternate
if (previousButton != ReplayButtonState.None) if (previousActions.Any())
{ {
Debug.Assert(previousButton != (ReplayButtonState.Left1 | ReplayButtonState.Right1), "Previous button state was not Left1 nor Right1 despite only using those two states.");
// Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button. // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
if (previousButton == button) if (previousActions.Contains(action))
{ {
button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button; action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton;
startFrame.ButtonState = button; startFrame.Actions.Clear();
startFrame.Actions.Add(action);
} }
// We always follow the most recent slider / spinner, so remove any other frames that occur while it exists. // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
@ -272,9 +271,14 @@ namespace osu.Game.Rulesets.Osu.Replays
// After alternating we need to keep holding the other button in the future rather than the previous one. // After alternating we need to keep holding the other button in the future rather than the previous one.
for (int j = index + 1; j < Frames.Count; ++j) for (int j = index + 1; j < Frames.Count; ++j)
{ {
var frame = (OsuReplayFrame)Frames[j];
// Don't affect frames which stop pressing a button! // Don't affect frames which stop pressing a button!
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton) if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions))
Frames[j].ButtonState = button; {
frame.Actions.Clear();
frame.Actions.Add(action);
}
} }
} }
} }
@ -298,16 +302,15 @@ namespace osu.Game.Rulesets.Osu.Replays
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
AddFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button)); AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
} }
t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection;
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
AddFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button)); AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
endFrame.MouseX = endPosition.X; endFrame.Position = endPosition;
endFrame.MouseY = endPosition.Y;
} }
else if (h is Slider) else if (h is Slider)
{ {
@ -316,10 +319,10 @@ namespace osu.Game.Rulesets.Osu.Replays
for (double j = FrameDelay; j < s.Duration; j += FrameDelay) for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
{ {
Vector2 pos = s.StackedPositionAt(j / s.Duration); Vector2 pos = s.StackedPositionAt(j / s.Duration);
AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button)); AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
} }
AddFrameToReplay(new ReplayFrame(s.EndTime, s.StackedEndPosition.X, s.StackedEndPosition.Y, button)); AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action));
} }
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!

View File

@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Replays
{
public class OsuReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
public Vector2 Position;
public List<OsuAction> Actions = new List<OsuAction>();
public OsuReplayFrame()
{
}
public OsuReplayFrame(double time, Vector2 position, params OsuAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
{
Position = legacyFrame.Position;
if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton);
}
}
}

View File

@ -2,32 +2,42 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
public class OsuReplayInputHandler : FramedReplayInputHandler public class OsuReplayInputHandler : FramedReplayInputHandler<OsuReplayFrame>
{ {
public OsuReplayInputHandler(Replay replay) public OsuReplayInputHandler(Replay replay)
: base(replay) : base(replay)
{ {
} }
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
protected Vector2? Position
{
get
{
if (!HasFrames)
return null;
return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
}
}
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates()
{ {
List<OsuAction> actions = new List<OsuAction>();
if (CurrentFrame?.MouseLeft ?? false) actions.Add(OsuAction.LeftButton);
if (CurrentFrame?.MouseRight ?? false) actions.Add(OsuAction.RightButton);
return new List<InputState> return new List<InputState>
{ {
new ReplayState<OsuAction> new ReplayState<OsuAction>
{ {
Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)), Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = actions PressedActions = CurrentFrame.Actions
} }
}; };
} }

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK; using OpenTK;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -48,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI
return null; return null;
} }
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
protected override Vector2 GetAspectAdjustedSize() protected override Vector2 GetAspectAdjustedSize()
{ {

View File

@ -126,6 +126,7 @@
<Compile Include="OsuDifficulty\Skills\Speed.cs" /> <Compile Include="OsuDifficulty\Skills\Speed.cs" />
<Compile Include="OsuDifficulty\Utils\History.cs" /> <Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" /> <Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayFrame.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" /> <Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\OsuBeatmapConversionTest.cs" /> <Compile Include="Tests\OsuBeatmapConversionTest.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" /> <Compile Include="Tests\TestCaseHitCircle.cs" />

View File

@ -35,15 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
bool hitButton = true; bool hitButton = true;
Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(-100000));
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
TaikoHitObject h = Beatmap.HitObjects[i]; TaikoHitObject h = Beatmap.HitObjects[i];
ReplayButtonState button;
IHasEndTime endTimeData = h as IHasEndTime; IHasEndTime endTimeData = h as IHasEndTime;
double endTime = endTimeData?.EndTime ?? h.StartTime; double endTime = endTimeData?.EndTime ?? h.StartTime;
@ -59,24 +57,26 @@ namespace osu.Game.Rulesets.Taiko.Replays
double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
for (double j = h.StartTime; j < endTime; j += hitRate) for (double j = h.StartTime; j < endTime; j += hitRate)
{ {
TaikoAction action;
switch (d) switch (d)
{ {
default: default:
case 0: case 0:
button = ReplayButtonState.Left1; action = TaikoAction.LeftCentre;
break; break;
case 1: case 1:
button = ReplayButtonState.Right1; action = TaikoAction.LeftRim;
break; break;
case 2: case 2:
button = ReplayButtonState.Left2; action = TaikoAction.RightCentre;
break; break;
case 3: case 3:
button = ReplayButtonState.Right2; action = TaikoAction.RightRim;
break; break;
} }
Frames.Add(new TaikoReplayFrame(j, button)); Frames.Add(new TaikoReplayFrame(j, action));
d = (d + 1) % 4; d = (d + 1) % 4;
if (++count == req) if (++count == req)
break; break;
@ -86,39 +86,39 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
hitButton = !hitButton; hitButton = !hitButton;
} }
} }
else if (hit != null) else if (hit != null)
{ {
TaikoAction[] actions;
if (hit is CentreHit) if (hit is CentreHit)
{ {
if (h.IsStrong) actions = h.IsStrong
button = ReplayButtonState.Left1 | ReplayButtonState.Left2; ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
else : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
} }
else else
{ {
if (h.IsStrong) actions = h.IsStrong
button = ReplayButtonState.Right1 | ReplayButtonState.Right2; ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
else : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
button = hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2;
} }
Frames.Add(new TaikoReplayFrame(h.StartTime, button)); Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
} }
else else
throw new InvalidOperationException("Unknown hit object type."); throw new InvalidOperationException("Unknown hit object type.");
Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY));
if (i < Beatmap.HitObjects.Count - 1) if (i < Beatmap.HitObjects.Count - 1)
{ {
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime) if (waitTime > endTime)
Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(waitTime));
} }
hitButton = !hitButton; hitButton = !hitButton;

View File

@ -3,31 +3,20 @@
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input; using osu.Framework.Input;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {
internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler<TaikoReplayFrame>
{ {
public TaikoFramedReplayInputHandler(Replay replay) public TaikoFramedReplayInputHandler(Replay replay)
: base(replay) : base(replay)
{ {
} }
public override List<InputState> GetPendingStates() protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
{
var actions = new List<TaikoAction>();
if (CurrentFrame?.MouseRight1 == true) public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
actions.Add(TaikoAction.LeftRim);
if (CurrentFrame?.MouseRight2 == true)
actions.Add(TaikoAction.RightRim);
if (CurrentFrame?.MouseLeft1 == true)
actions.Add(TaikoAction.LeftCentre);
if (CurrentFrame?.MouseLeft2 == true)
actions.Add(TaikoAction.RightCentre);
return new List<InputState> { new ReplayState<TaikoAction> { PressedActions = actions } };
}
} }
} }

View File

@ -1,17 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {
public class TaikoReplayFrame : ReplayFrame public class TaikoReplayFrame : ReplayFrame, IConvertibleReplayFrame
{ {
public override bool IsImportant => MouseLeft || MouseRight; public List<TaikoAction> Actions = new List<TaikoAction>();
public TaikoReplayFrame(double time, ReplayButtonState buttons) public TaikoReplayFrame()
: base(time, null, null, buttons)
{ {
} }
public TaikoReplayFrame(double time, params TaikoAction[] actions)
: base(time)
{
Actions.AddRange(actions);
}
public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
{
if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
}
} }
} }

View File

@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
namespace osu.Game.Rulesets.Taiko namespace osu.Game.Rulesets.Taiko
{ {
@ -103,6 +105,8 @@ namespace osu.Game.Rulesets.Taiko
public override int LegacyID => 1; public override int LegacyID => 1;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
public TaikoRuleset(RulesetInfo rulesetInfo = null) public TaikoRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.Taiko.Replays;
using OpenTK; using OpenTK;
using System.Linq; using System.Linq;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
@ -133,6 +134,6 @@ namespace osu.Game.Rulesets.Taiko.UI
return null; return null;
} }
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual
// We create a dummy RulesetContainer just to get the replay - we don't want to use mods here // 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 // to simulate setting a replay rather than having the replay already set for us
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, false); var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo));
// We have the replay // We have the replay
var replay = dummyRulesetContainer.Replay; var replay = dummyRulesetContainer.Replay;

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using OpenTK; using OpenTK;
using OpenTK.Input; using OpenTK.Input;
@ -17,14 +16,15 @@ namespace osu.Game.Rulesets.Replays
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. /// 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. /// It handles logic of any frames which *must* be executed.
/// </summary> /// </summary>
public abstract class FramedReplayInputHandler : ReplayInputHandler public abstract class FramedReplayInputHandler<TFrame> : ReplayInputHandler
where TFrame : ReplayFrame
{ {
private readonly Replay replay; private readonly Replay replay;
protected List<ReplayFrame> Frames => replay.Frames; protected List<ReplayFrame> Frames => replay.Frames;
public ReplayFrame CurrentFrame => !hasFrames ? null : Frames[currentFrameIndex]; public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex];
public ReplayFrame NextFrame => !hasFrames ? null : Frames[nextFrameIndex]; public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex];
private int currentFrameIndex; private int currentFrameIndex;
@ -46,31 +46,14 @@ namespace osu.Game.Rulesets.Replays
return true; return true;
} }
public void SetPosition(Vector2 pos)
{
}
protected Vector2? Position
{
get
{
if (!hasFrames)
return null;
return Interpolation.ValueAt(currentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
}
}
public override List<InputState> GetPendingStates() => new List<InputState>(); public override List<InputState> GetPendingStates() => new List<InputState>();
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
public bool AtFirstFrame => currentFrameIndex == 0; public bool AtFirstFrame => currentFrameIndex == 0;
public Vector2 Size => new Vector2(512, 384);
private const double sixty_frame_time = 1000.0 / 60; private const double sixty_frame_time = 1000.0 / 60;
private double currentTime; protected double CurrentTime { get; private set; }
private int currentDirection; private int currentDirection;
/// <summary> /// <summary>
@ -79,14 +62,16 @@ namespace osu.Game.Rulesets.Replays
/// </summary> /// </summary>
public bool FrameAccuratePlayback = true; public bool FrameAccuratePlayback = true;
private bool hasFrames => Frames.Count > 0; protected bool HasFrames => Frames.Count > 0;
private bool inImportantSection => private bool inImportantSection =>
FrameAccuratePlayback && HasFrames && FrameAccuratePlayback &&
//a button is in a pressed state //a button is in a pressed state
((currentDirection > 0 ? CurrentFrame : NextFrame)?.IsImportant ?? false) && IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) &&
//the next frame is within an allowable time span //the next frame is within an allowable time span
Math.Abs(currentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2;
protected virtual bool IsImportant(TFrame frame) => false;
/// <summary> /// <summary>
/// Update the current frame based on an incoming time value. /// Update the current frame based on an incoming time value.
@ -97,10 +82,10 @@ namespace osu.Game.Rulesets.Replays
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns> /// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
public override double? SetFrameFromTime(double time) public override double? SetFrameFromTime(double time)
{ {
currentDirection = time.CompareTo(currentTime); currentDirection = time.CompareTo(CurrentTime);
if (currentDirection == 0) currentDirection = 1; if (currentDirection == 0) currentDirection = 1;
if (hasFrames) if (HasFrames)
{ {
// check if the next frame is in the "future" for the current playback direction // check if the next frame is in the "future" for the current playback direction
if (currentDirection != time.CompareTo(NextFrame.Time)) if (currentDirection != time.CompareTo(NextFrame.Time))
@ -114,12 +99,12 @@ namespace osu.Game.Rulesets.Replays
// If going backwards, we need to execute once _before_ the frame time to reverse any judgements // If going backwards, we need to execute once _before_ the frame time to reverse any judgements
// that would occur as a result of this frame in forward playback // that would occur as a result of this frame in forward playback
if (currentDirection == -1) if (currentDirection == -1)
return currentTime = CurrentFrame.Time - 1; return CurrentTime = CurrentFrame.Time - 1;
return currentTime = CurrentFrame.Time; return CurrentTime = CurrentFrame.Time;
} }
} }
return currentTime = time; return CurrentTime = time;
} }
protected class ReplayMouseState : MouseState protected class ReplayMouseState : MouseState

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
namespace osu.Game.Rulesets.Replays.Legacy
{
public class LegacyReplayFrame : ReplayFrame
{
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
public float? MouseX;
public float? MouseY;
public bool MouseLeft => MouseLeft1 || MouseLeft2;
public bool MouseRight => MouseRight1 || MouseRight2;
public bool MouseLeft1 => (ButtonState & ReplayButtonState.Left1) > 0;
public bool MouseRight1 => (ButtonState & ReplayButtonState.Right1) > 0;
public bool MouseLeft2 => (ButtonState & ReplayButtonState.Left2) > 0;
public bool MouseRight2 => (ButtonState & ReplayButtonState.Right2) > 0;
public ReplayButtonState ButtonState;
public LegacyReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState)
: base(time)
{
MouseX = mouseX;
MouseY = mouseY;
ButtonState = buttonState;
}
public override string ToString()
{
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
}
}
}

View File

@ -3,7 +3,7 @@
using System; using System;
namespace osu.Game.Rulesets.Replays namespace osu.Game.Rulesets.Replays.Legacy
{ {
[Flags] [Flags]
public enum ReplayButtonState public enum ReplayButtonState

View File

@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Replays
public class Replay public class Replay
{ {
public User User; public User User;
public List<ReplayFrame> Frames = new List<ReplayFrame>(); public List<ReplayFrame> Frames = new List<ReplayFrame>();
} }
} }

View File

@ -1,70 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
namespace osu.Game.Rulesets.Replays namespace osu.Game.Rulesets.Replays
{ {
public class ReplayFrame public class ReplayFrame
{ {
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
public virtual bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
public float? MouseX;
public float? MouseY;
public bool MouseLeft => MouseLeft1 || MouseLeft2;
public bool MouseRight => MouseRight1 || MouseRight2;
public bool MouseLeft1
{
get { return (ButtonState & ReplayButtonState.Left1) > 0; }
set { setButtonState(ReplayButtonState.Left1, value); }
}
public bool MouseRight1
{
get { return (ButtonState & ReplayButtonState.Right1) > 0; }
set { setButtonState(ReplayButtonState.Right1, value); }
}
public bool MouseLeft2
{
get { return (ButtonState & ReplayButtonState.Left2) > 0; }
set { setButtonState(ReplayButtonState.Left2, value); }
}
public bool MouseRight2
{
get { return (ButtonState & ReplayButtonState.Right2) > 0; }
set { setButtonState(ReplayButtonState.Right2, value); }
}
private void setButtonState(ReplayButtonState singleButton, bool pressed)
{
if (pressed)
ButtonState |= singleButton;
else
ButtonState &= ~singleButton;
}
public double Time; public double Time;
public ReplayButtonState ButtonState; public ReplayFrame()
protected ReplayFrame()
{ {
} }
public ReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) public ReplayFrame(double time)
{ {
MouseX = mouseX;
MouseY = mouseY;
ButtonState = buttonState;
Time = time; Time = time;
} }
public override string ToString()
{
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
}
} }
} }

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Replays.Legacy;
namespace osu.Game.Rulesets.Replays.Types
{
/// <summary>
/// A type of <see cref="ReplayFrame"/> which can be converted from a <see cref="LegacyReplayFrame"/>.
/// </summary>
public interface IConvertibleReplayFrame
{
/// <summary>
/// Populates this <see cref="ReplayFrame"/> using values from a <see cref="LegacyReplayFrame"/>.
/// </summary>
/// <param name="legacyFrame">The <see cref="LegacyReplayFrame"/> to extract values from.</param>
/// <param name="score">The score.</param>
/// <param name="beatmap">The beatmap.</param>
void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap);
}
}

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -89,6 +90,13 @@ namespace osu.Game.Rulesets
/// <returns>A descriptive name of the variant.</returns> /// <returns>A descriptive name of the variant.</returns>
public virtual string GetVariantName(int variant) => string.Empty; public virtual string GetVariantName(int variant) => string.Empty;
/// <summary>
/// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame
/// for conversion use.
/// </summary>
/// <returns>An empty frame for the current ruleset, or null if unsupported.</returns>
public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null;
/// <summary> /// <summary>
/// Create a ruleset info based on this ruleset. /// Create a ruleset info based on this ruleset.
/// </summary> /// </summary>

View File

@ -0,0 +1,152 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.IO;
using osu.Game.Beatmaps;
using osu.Game.IO.Legacy;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Users;
using SharpCompress.Compressors.LZMA;
namespace osu.Game.Rulesets.Scoring.Legacy
{
public class LegacyScoreParser
{
private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps;
public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
}
private Beatmap currentBeatmap;
private Ruleset currentRuleset;
public Score Parse(Stream stream)
{
Score score;
using (SerializationReader sr = new SerializationReader(stream))
{
score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) };
currentRuleset = score.Ruleset.CreateInstance();
/* score.Pass = true;*/
var version = sr.ReadInt32();
/* score.FileChecksum = */
var beatmapHash = sr.ReadString();
score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
/* score.PlayerName = */
score.User = new User { Username = sr.ReadString() };
/* var localScoreChecksum = */
sr.ReadString();
/* score.Count300 = */
sr.ReadUInt16();
/* score.Count100 = */
sr.ReadUInt16();
/* score.Count50 = */
sr.ReadUInt16();
/* score.CountGeki = */
sr.ReadUInt16();
/* score.CountKatu = */
sr.ReadUInt16();
/* score.CountMiss = */
sr.ReadUInt16();
score.TotalScore = sr.ReadInt32();
score.MaxCombo = sr.ReadUInt16();
/* score.Perfect = */
sr.ReadBoolean();
/* score.EnabledMods = (Mods)*/
sr.ReadInt32();
/* score.HpGraphString = */
sr.ReadString();
/* score.Date = */
sr.ReadDateTime();
var compressedReplay = sr.ReadByteArray();
if (version >= 20140721)
/*OnlineId =*/
sr.ReadInt64();
else if (version >= 20121008)
/*OnlineId =*/
sr.ReadInt32();
using (var replayInStream = new MemoryStream(compressedReplay))
{
byte[] properties = new byte[5];
if (replayInStream.Read(properties, 0, 5) != 5)
throw new IOException("input .lzma is too short");
long outSize = 0;
for (int i = 0; i < 8; i++)
{
int v = replayInStream.ReadByte();
if (v < 0)
throw new IOException("Can't Read 1");
outSize |= (long)(byte)v << (8 * i);
}
long compressedSize = replayInStream.Length - replayInStream.Position;
using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize))
using (var reader = new StreamReader(lzma))
{
score.Replay = new Replay { User = score.User };
readLegacyReplay(score.Replay, reader);
}
}
}
return score;
}
private void readLegacyReplay(Replay replay, StreamReader reader)
{
float lastTime = 0;
foreach (var l in reader.ReadToEnd().Split(','))
{
var split = l.Split('|');
if (split.Length < 4)
continue;
if (split[0] == "-12345")
{
// Todo: The seed is provided in split[3], which we'll need to use at some point
continue;
}
var diff = float.Parse(split[0]);
lastTime += diff;
// Todo: At some point we probably want to rewind and play back the negative-time frames
// but for now we'll achieve equal playback to stable by skipping negative frames
if (diff < 0)
continue;
replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3]))));
}
}
private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame)
{
var convertible = currentRuleset.CreateConvertibleReplayFrame();
if (convertible == null)
throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}");
convertible.ConvertFrom(legacyFrame, currentBeatmap);
var frame = (ReplayFrame)convertible;
frame.Time = legacyFrame.Time;
return frame;
}
}
}

View File

@ -2,16 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Legacy;
using osu.Game.IPC; using osu.Game.IPC;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Users;
using SharpCompress.Compressors.LZMA;
namespace osu.Game.Rulesets.Scoring namespace osu.Game.Rulesets.Scoring
{ {
@ -53,127 +49,8 @@ namespace osu.Game.Rulesets.Scoring
public Score ReadReplayFile(string replayFilename) public Score ReadReplayFile(string replayFilename)
{ {
Score score;
using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
using (SerializationReader sr = new SerializationReader(s)) return new LegacyScoreParser(rulesets, beatmaps).Parse(s);
{
score = new Score
{
Ruleset = rulesets.GetRuleset(sr.ReadByte())
};
/* score.Pass = true;*/
var version = sr.ReadInt32();
/* score.FileChecksum = */
var beatmapHash = sr.ReadString();
score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
/* score.PlayerName = */
score.User = new User { Username = sr.ReadString() };
/* var localScoreChecksum = */
sr.ReadString();
/* score.Count300 = */
sr.ReadUInt16();
/* score.Count100 = */
sr.ReadUInt16();
/* score.Count50 = */
sr.ReadUInt16();
/* score.CountGeki = */
sr.ReadUInt16();
/* score.CountKatu = */
sr.ReadUInt16();
/* score.CountMiss = */
sr.ReadUInt16();
score.TotalScore = sr.ReadInt32();
score.MaxCombo = sr.ReadUInt16();
/* score.Perfect = */
sr.ReadBoolean();
/* score.EnabledMods = (Mods)*/
sr.ReadInt32();
/* score.HpGraphString = */
sr.ReadString();
/* score.Date = */
sr.ReadDateTime();
var compressedReplay = sr.ReadByteArray();
if (version >= 20140721)
/*OnlineId =*/
sr.ReadInt64();
else if (version >= 20121008)
/*OnlineId =*/
sr.ReadInt32();
using (var replayInStream = new MemoryStream(compressedReplay))
{
byte[] properties = new byte[5];
if (replayInStream.Read(properties, 0, 5) != 5)
throw new IOException("input .lzma is too short");
long outSize = 0;
for (int i = 0; i < 8; i++)
{
int v = replayInStream.ReadByte();
if (v < 0)
throw new IOException("Can't Read 1");
outSize |= (long)(byte)v << (8 * i);
}
long compressedSize = replayInStream.Length - replayInStream.Position;
using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize))
using (var reader = new StreamReader(lzma))
{
score.Replay = createLegacyReplay(reader);
score.Replay.User = score.User;
}
}
}
return score;
} }
/// <summary>
/// Creates a legacy replay which is read from a stream.
/// </summary>
/// <param name="reader">The stream reader.</param>
/// <returns>The legacy replay.</returns>
private Replay createLegacyReplay(StreamReader reader)
{
var frames = new List<ReplayFrame>();
float lastTime = 0;
foreach (var l in reader.ReadToEnd().Split(','))
{
var split = l.Split('|');
if (split.Length < 4)
continue;
if (split[0] == "-12345")
{
// Todo: The seed is provided in split[3], which we'll need to use at some point
continue;
}
var diff = float.Parse(split[0]);
lastTime += diff;
// Todo: At some point we probably want to rewind and play back the negative-time frames
// but for now we'll achieve equal playback to stable by skipping negative frames
if (diff < 0)
continue;
frames.Add(new ReplayFrame(
lastTime,
float.Parse(split[1]),
float.Parse(split[2]),
(ReplayButtonState)int.Parse(split[3])
));
}
return new Replay { Frames = frames };
}
} }
} }

View File

@ -17,6 +17,7 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
@ -110,7 +111,7 @@ namespace osu.Game.Rulesets.UI
/// <returns>The input manager.</returns> /// <returns>The input manager.</returns>
public abstract PassThroughInputManager CreateInputManager(); public abstract PassThroughInputManager CreateInputManager();
protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => null; protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
public Replay Replay { get; private set; } public Replay Replay { get; private set; }

View File

@ -365,6 +365,11 @@
<Compile Include="Overlays\Social\SocialPanel.cs" /> <Compile Include="Overlays\Social\SocialPanel.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" /> <Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\HitWindows.cs" /> <Compile Include="Rulesets\Objects\HitWindows.cs" />
<Compile Include="Rulesets\Replays\Legacy\LegacyReplayFrame.cs" />
<Compile Include="Rulesets\Replays\Legacy\ReplayButtonState.cs" />
<Compile Include="Rulesets\Replays\ReplayFrame.cs" />
<Compile Include="Rulesets\Replays\Types\IConvertibleReplayFrame.cs" />
<Compile Include="Rulesets\Scoring\Legacy\LegacyScoreParser.cs" />
<Compile Include="Rulesets\UI\ScalableContainer.cs" /> <Compile Include="Rulesets\UI\ScalableContainer.cs" />
<Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" /> <Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" />
<Compile Include="Rulesets\Objects\CatmullApproximator.cs" /> <Compile Include="Rulesets\Objects\CatmullApproximator.cs" />
@ -710,8 +715,6 @@
<Compile Include="Rulesets\Replays\FramedReplayInputHandler.cs" /> <Compile Include="Rulesets\Replays\FramedReplayInputHandler.cs" />
<Compile Include="Rulesets\Replays\IAutoGenerator.cs" /> <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
<Compile Include="Rulesets\Replays\Replay.cs" /> <Compile Include="Rulesets\Replays\Replay.cs" />
<Compile Include="Rulesets\Replays\ReplayButtonState.cs" />
<Compile Include="Rulesets\Replays\ReplayFrame.cs" />
<Compile Include="Rulesets\Ruleset.cs" /> <Compile Include="Rulesets\Ruleset.cs" />
<Compile Include="Rulesets\RulesetInfo.cs" /> <Compile Include="Rulesets\RulesetInfo.cs" />
<Compile Include="Rulesets\RulesetStore.cs" /> <Compile Include="Rulesets\RulesetStore.cs" />