mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 14:32:55 +08:00
Merge branch 'master' into rewrite-break-overlay
This commit is contained in:
commit
6a768e378a
@ -1 +1 @@
|
||||
Subproject commit e8ae207769ec26fb7ddd67a2433514fcda354ecd
|
||||
Subproject commit 6372fb22c1c85f600921a139849b8dedf71026d5
|
@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
@ -99,7 +101,9 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
|
||||
|
||||
public override int LegacyID => 2;
|
||||
public override int? LegacyID => 2;
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||
|
||||
public CatchRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
}
|
||||
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));
|
||||
}
|
||||
else if (dashRequired)
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
|
||||
|
||||
//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, h.X));
|
||||
}
|
||||
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -3,37 +3,51 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Replays
|
||||
{
|
||||
public class CatchFramedReplayInputHandler : FramedReplayInputHandler
|
||||
public class CatchFramedReplayInputHandler : FramedReplayInputHandler<CatchReplayFrame>
|
||||
{
|
||||
public CatchFramedReplayInputHandler(Replay 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()
|
||||
{
|
||||
if (!Position.HasValue) return new List<InputState>();
|
||||
|
||||
var action = new List<CatchAction>();
|
||||
var actions = new List<CatchAction>();
|
||||
|
||||
if (CurrentFrame.ButtonState == ReplayButtonState.Left1)
|
||||
action.Add(CatchAction.Dash);
|
||||
if (CurrentFrame.Dashing)
|
||||
actions.Add(CatchAction.Dash);
|
||||
|
||||
if (Position.Value.X > CurrentFrame.Position.X)
|
||||
action.Add(CatchAction.MoveRight);
|
||||
else if (Position.Value.X < CurrentFrame.Position.X)
|
||||
action.Add(CatchAction.MoveLeft);
|
||||
if (Position.Value > CurrentFrame.Position)
|
||||
actions.Add(CatchAction.MoveRight);
|
||||
else if (Position.Value < CurrentFrame.Position)
|
||||
actions.Add(CatchAction.MoveLeft);
|
||||
|
||||
return new List<InputState>
|
||||
{
|
||||
new CatchReplayState
|
||||
{
|
||||
PressedActions = action,
|
||||
CatcherX = Position.Value.X
|
||||
PressedActions = actions,
|
||||
CatcherX = Position.Value
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,17 +1,34 @@
|
||||
// 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.Catch.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
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)
|
||||
: base(time, x ?? -1, null, button)
|
||||
public CatchReplayFrame()
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawable;
|
||||
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
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();
|
||||
|
||||
|
@ -14,5 +14,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
/// The number of <see cref="Column"/>s which this stage contains.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
@ -112,7 +114,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);
|
||||
|
||||
public override int LegacyID => 3;
|
||||
public override int? LegacyID => 3;
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
|
||||
|
||||
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
return new Score
|
||||
{
|
||||
User = new User { Username = "osu!topus!" },
|
||||
Replay = new ManiaAutoGenerator(beatmap).Generate(),
|
||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@ -15,10 +15,31 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
@ -30,18 +51,18 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
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 point in group)
|
||||
{
|
||||
if (point is HitPoint)
|
||||
activeColumns |= 1 << point.Column;
|
||||
actions.Add(columnActions[point.Column]);
|
||||
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;
|
||||
|
@ -4,40 +4,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Replays
|
||||
{
|
||||
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
|
||||
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler<ManiaReplayFrame>
|
||||
{
|
||||
private readonly ManiaRulesetContainer container;
|
||||
|
||||
public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
|
||||
public ManiaFramedReplayInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
private ManiaPlayfield playfield;
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
var actions = new List<ManiaAction>();
|
||||
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
if (playfield == null)
|
||||
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 } };
|
||||
}
|
||||
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,59 @@
|
||||
// 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.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
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)
|
||||
: base(time, activeColumns, null, ReplayButtonState.None)
|
||||
public ManiaReplayFrame()
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
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 });
|
||||
|
||||
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.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit 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.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
|
||||
}
|
||||
|
||||
[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 });
|
||||
|
||||
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.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit 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.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
|
||||
}
|
||||
|
||||
[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, Column = 1 });
|
||||
|
||||
@ -68,8 +70,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||
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(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
|
||||
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
}
|
||||
|
||||
[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, Column = 1 });
|
||||
|
||||
@ -90,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||
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(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
|
||||
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
}
|
||||
|
||||
[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 = 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(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(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
|
||||
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released");
|
||||
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed");
|
||||
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
|
||||
}
|
||||
|
||||
[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 = 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(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(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
|
||||
Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed");
|
||||
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released");
|
||||
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 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]
|
||||
@ -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 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(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(1, generated.Frames[1].MouseX, "Key 1 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.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has 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));
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
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 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);
|
||||
}
|
||||
|
@ -48,13 +48,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private Color4 specialColumnColour;
|
||||
|
||||
private readonly int firstColumnIndex;
|
||||
private readonly StageDefinition definition;
|
||||
|
||||
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
: base(ScrollingDirection.Up)
|
||||
{
|
||||
this.firstColumnIndex = firstColumnIndex;
|
||||
this.definition = definition;
|
||||
|
||||
Name = "Stage";
|
||||
|
||||
@ -131,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
for (int i = 0; i < definition.Columns; i++)
|
||||
{
|
||||
var isSpecial = isSpecialColumn(i);
|
||||
var isSpecial = definition.IsSpecialColumn(i);
|
||||
var column = new Column
|
||||
{
|
||||
IsSpecial = isSpecial,
|
||||
@ -160,13 +158,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
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)
|
||||
{
|
||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
|
||||
|
||||
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
|
||||
SpinsRequired = (int)(SpinsRequired * 0.6);
|
||||
SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
@ -143,7 +145,9 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
||||
|
||||
public override int LegacyID => 0;
|
||||
public override int? LegacyID => 0;
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
|
||||
|
||||
public OsuRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
|
@ -6,7 +6,7 @@ using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@ -64,9 +64,9 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
buttonIndex = 0;
|
||||
|
||||
AddFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None));
|
||||
AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None));
|
||||
AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None));
|
||||
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
|
||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
||||
|
||||
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).
|
||||
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 (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.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 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)
|
||||
{
|
||||
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 (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.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 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)
|
||||
{
|
||||
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 (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.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 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?
|
||||
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)
|
||||
{
|
||||
@ -192,13 +192,13 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
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.
|
||||
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
|
||||
{
|
||||
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;
|
||||
@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
// Time to insert the first frame which clicks the object
|
||||
// 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.
|
||||
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
|
||||
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
|
||||
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!
|
||||
if (index >= 0)
|
||||
{
|
||||
ReplayFrame previousFrame = Frames[index];
|
||||
var previousButton = previousFrame.ButtonState;
|
||||
var previousFrame = (OsuReplayFrame)Frames[index];
|
||||
var previousActions = previousFrame.Actions;
|
||||
|
||||
// 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.
|
||||
if (previousButton == button)
|
||||
if (previousActions.Contains(action))
|
||||
{
|
||||
button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button;
|
||||
startFrame.ButtonState = button;
|
||||
action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton;
|
||||
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.
|
||||
@ -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.
|
||||
for (int j = index + 1; j < Frames.Count; ++j)
|
||||
{
|
||||
var frame = (OsuReplayFrame)Frames[j];
|
||||
|
||||
// Don't affect frames which stop pressing a button!
|
||||
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
|
||||
Frames[j].ButtonState = button;
|
||||
if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions))
|
||||
{
|
||||
frame.Actions.Clear();
|
||||
frame.Actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,16 +302,15 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
|
||||
|
||||
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;
|
||||
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.MouseY = endPosition.Y;
|
||||
endFrame.Position = endPosition;
|
||||
}
|
||||
else if (h is Slider)
|
||||
{
|
||||
@ -316,10 +319,10 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
|
||||
{
|
||||
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!
|
||||
|
36
osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
Normal file
36
osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,32 +2,42 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public class OsuReplayInputHandler : FramedReplayInputHandler
|
||||
public class OsuReplayInputHandler : FramedReplayInputHandler<OsuReplayFrame>
|
||||
{
|
||||
public OsuReplayInputHandler(Replay 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()
|
||||
{
|
||||
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>
|
||||
{
|
||||
new ReplayState<OsuAction>
|
||||
{
|
||||
Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)),
|
||||
PressedActions = actions
|
||||
PressedActions = CurrentFrame.Actions
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using OpenTK;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -48,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
|
||||
|
||||
protected override Vector2 GetAspectAdjustedSize()
|
||||
{
|
||||
|
@ -126,6 +126,7 @@
|
||||
<Compile Include="OsuDifficulty\Skills\Speed.cs" />
|
||||
<Compile Include="OsuDifficulty\Utils\History.cs" />
|
||||
<Compile Include="OsuInputManager.cs" />
|
||||
<Compile Include="Replays\OsuReplayFrame.cs" />
|
||||
<Compile Include="Replays\OsuReplayInputHandler.cs" />
|
||||
<Compile Include="Tests\OsuBeatmapConversionTest.cs" />
|
||||
<Compile Include="Tests\TestCaseHitCircle.cs" />
|
||||
|
@ -101,16 +101,16 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
// The duration of the taiko hit object
|
||||
double taikoDuration = distance / taikoVelocity;
|
||||
|
||||
// For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but
|
||||
// only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here.
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
|
||||
speedAdjustedBeatLength *= speedAdjustment;
|
||||
|
||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
|
||||
// The duration of the osu! hit object
|
||||
double osuDuration = distance / osuVelocity;
|
||||
|
||||
// osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
|
||||
// only uses it for tick rate if beatmap version < 8
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
|
||||
speedAdjustedBeatLength *= speedAdjustment;
|
||||
|
||||
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
||||
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
|
||||
|
||||
|
@ -82,8 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return;
|
||||
|
||||
int countHit = NestedHitObjects.Count(o => o.IsHit);
|
||||
|
||||
if (countHit > HitObject.RequiredGoodHits)
|
||||
if (countHit >= HitObject.RequiredGoodHits)
|
||||
{
|
||||
AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good });
|
||||
if (HitObject.IsStrong)
|
||||
|
@ -35,15 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
{
|
||||
bool hitButton = true;
|
||||
|
||||
Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None));
|
||||
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None));
|
||||
Frames.Add(new TaikoReplayFrame(-100000));
|
||||
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
|
||||
|
||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
TaikoHitObject h = Beatmap.HitObjects[i];
|
||||
|
||||
ReplayButtonState button;
|
||||
|
||||
IHasEndTime endTimeData = h as IHasEndTime;
|
||||
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);
|
||||
for (double j = h.StartTime; j < endTime; j += hitRate)
|
||||
{
|
||||
TaikoAction action;
|
||||
|
||||
switch (d)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
button = ReplayButtonState.Left1;
|
||||
action = TaikoAction.LeftCentre;
|
||||
break;
|
||||
case 1:
|
||||
button = ReplayButtonState.Right1;
|
||||
action = TaikoAction.LeftRim;
|
||||
break;
|
||||
case 2:
|
||||
button = ReplayButtonState.Left2;
|
||||
action = TaikoAction.RightCentre;
|
||||
break;
|
||||
case 3:
|
||||
button = ReplayButtonState.Right2;
|
||||
action = TaikoAction.RightRim;
|
||||
break;
|
||||
}
|
||||
|
||||
Frames.Add(new TaikoReplayFrame(j, button));
|
||||
Frames.Add(new TaikoReplayFrame(j, action));
|
||||
d = (d + 1) % 4;
|
||||
if (++count == req)
|
||||
break;
|
||||
@ -86,39 +86,39 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
else if (hit != null)
|
||||
{
|
||||
TaikoAction[] actions;
|
||||
|
||||
if (hit is CentreHit)
|
||||
{
|
||||
if (h.IsStrong)
|
||||
button = ReplayButtonState.Left1 | ReplayButtonState.Left2;
|
||||
else
|
||||
button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
|
||||
actions = h.IsStrong
|
||||
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
||||
: new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (h.IsStrong)
|
||||
button = ReplayButtonState.Right1 | ReplayButtonState.Right2;
|
||||
else
|
||||
button = hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2;
|
||||
actions = h.IsStrong
|
||||
? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
|
||||
: new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
|
||||
}
|
||||
|
||||
Frames.Add(new TaikoReplayFrame(h.StartTime, button));
|
||||
Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
|
||||
}
|
||||
else
|
||||
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)
|
||||
{
|
||||
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
|
||||
if (waitTime > endTime)
|
||||
Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None));
|
||||
Frames.Add(new TaikoReplayFrame(waitTime));
|
||||
}
|
||||
|
||||
hitButton = !hitButton;
|
||||
|
@ -3,31 +3,20 @@
|
||||
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Replays
|
||||
{
|
||||
internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler
|
||||
internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler<TaikoReplayFrame>
|
||||
{
|
||||
public TaikoFramedReplayInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
}
|
||||
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
var actions = new List<TaikoAction>();
|
||||
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
if (CurrentFrame?.MouseRight1 == true)
|
||||
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 } };
|
||||
}
|
||||
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,34 @@
|
||||
// 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;
|
||||
|
||||
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)
|
||||
: base(time, null, null, buttons)
|
||||
public TaikoReplayFrame()
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 6590,
|
||||
"Objects": [{
|
||||
"StartTime": 6590,
|
||||
"EndTime": 8320,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 8436,
|
||||
"Objects": [{
|
||||
"StartTime": 8436,
|
||||
"EndTime": 10166,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 10282,
|
||||
"Objects": [{
|
||||
"StartTime": 10282,
|
||||
"EndTime": 12012,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 12128,
|
||||
"Objects": [{
|
||||
"StartTime": 12128,
|
||||
"EndTime": 13858,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 41666,
|
||||
"Objects": [{
|
||||
"StartTime": 41666,
|
||||
"EndTime": 42589,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 62666,
|
||||
"Objects": [{
|
||||
"StartTime": 62666,
|
||||
"EndTime": 63127,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 208743,
|
||||
"Objects": [{
|
||||
"StartTime": 208743,
|
||||
"EndTime": 209204,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
osu file format v14
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4.2
|
||||
OverallDifficulty:9
|
||||
ApproachRate:9.8
|
||||
SliderMultiplier:1.87
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
6590,461.538461538462,4,2,2,15,1,0
|
||||
6590,-200,4,2,2,15,0,0
|
||||
49051,230.769230769231,4,2,1,15,1,0
|
||||
62666,-200,4,2,1,60,0,0
|
||||
197666,-100,4,2,1,85,0,1
|
||||
|
||||
[HitObjects]
|
||||
88,104,6590,6,0,B|176:156|256:108|256:108|336:60|423:112,1,350.625,6|0,0:0|0:0,0:0:0:0:
|
||||
396,213,8436,2,0,P|277:247|376:172,1,350.625,6|0,0:0|0:0,0:0:0:0:
|
||||
472,220,10282,2,0,P|456:288|220:300,1,350.625,6|0,0:0|0:0,0:0:0:0:
|
||||
277,200,12128,2,0,P|398:225|276:244,1,350.625,6|0,0:0|0:0,0:0:0:0:
|
||||
268,229,41666,2,0,L|473:210,1,187,2|2,0:0|0:0,0:0:0:0:
|
||||
133,342,62666,2,0,B|132:316|132:316|128:316|128:316|130:295|130:295|126:296|126:296|129:275|129:275|125:275|125:275|127:254|127:254|123:255|123:255|125:234|125:234|121:234|121:234|123:213|123:213|119:214|119:214|121:193|121:193|118:193|118:193|118:172,1,187,8|8,0:0|0:0,0:0:0:0:
|
||||
481,338,208743,6,0,P|492:262|383:195,2,187,2|8|2,0:0|0:0|0:0,0:0:0:0:
|
@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
@ -101,7 +103,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
|
||||
|
||||
public override int LegacyID => 1;
|
||||
public override int? LegacyID => 1;
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||
|
||||
public TaikoRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
[NonParallelizable]
|
||||
[TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
|
||||
[TestCase("slider-generating-drumroll", false)]
|
||||
public void Test(string name, bool isForCurrentRuleset)
|
||||
{
|
||||
this.isForCurrentRuleset = isForCurrentRuleset;
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Taiko.Replays;
|
||||
using OpenTK;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
@ -133,6 +134,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,8 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Testing\Beatmaps\basic-expected-conversion.json" />
|
||||
<EmbeddedResource Include="Resources\Testing\Beatmaps\basic.osu" />
|
||||
<EmbeddedResource Include="Resources\Testing\Beatmaps\slider-generating-drumroll-expected-conversion.json" />
|
||||
<EmbeddedResource Include="Resources\Testing\Beatmaps\slider-generating-drumroll.osu" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('$(SolutionDir)\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.8\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeBeatmapGeneral()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(4, difficulty.CircleSize);
|
||||
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
Assert.AreEqual(9, difficulty.ApproachRate);
|
||||
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeBeatmapEvents()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeBeatmapTimingPoints()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjects()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(4, difficulty.CircleSize);
|
||||
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
Assert.AreEqual(9, difficulty.ApproachRate);
|
||||
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var sr = new StreamReader(stream))
|
||||
{
|
||||
|
||||
var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr);
|
||||
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.DecodeBeatmap(sr);
|
||||
using (var ms = new MemoryStream())
|
||||
using (var sw = new StreamWriter(ms))
|
||||
using (var sr2 = new StreamReader(ms))
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
Assert.AreEqual("Deif", meta.AuthorString);
|
||||
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
||||
Assert.AreEqual(164471, meta.PreviewTime);
|
||||
Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime);
|
||||
Assert.AreEqual(string.Empty, meta.Source);
|
||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
||||
Assert.AreEqual("Renatus", meta.Title);
|
||||
|
@ -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
|
||||
// to simulate setting a replay rather than having the replay already set for us
|
||||
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
|
||||
var replay = dummyRulesetContainer.Replay;
|
||||
|
30
osu.Game.Tests/Visual/TestCaseVolumePieces.cs
Normal file
30
osu.Game.Tests/Visual/TestCaseVolumePieces.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseVolumePieces : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) };
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
VolumeMeter meter;
|
||||
MuteButton mute;
|
||||
Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue));
|
||||
Add(mute = new MuteButton
|
||||
{
|
||||
Margin = new MarginPadding { Top = 200 }
|
||||
});
|
||||
|
||||
AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
|
||||
AddToggleStep("mute", b => mute.Current.Value = b);
|
||||
}
|
||||
}
|
||||
}
|
@ -174,6 +174,7 @@
|
||||
<Compile Include="Visual\TestCaseUserPanel.cs" />
|
||||
<Compile Include="Visual\TestCaseUserProfile.cs" />
|
||||
<Compile Include="Visual\TestCaseUserRanks.cs" />
|
||||
<Compile Include="Visual\TestCaseVolumePieces.cs" />
|
||||
<Compile Include="Visual\TestCaseWaveform.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -29,8 +29,8 @@ namespace osu.Game.Beatmaps
|
||||
set => approachRate = value;
|
||||
}
|
||||
|
||||
public float SliderMultiplier { get; set; } = 1;
|
||||
public float SliderTickRate { get; set; } = 1;
|
||||
public double SliderMultiplier { get; set; } = 1;
|
||||
public double SliderTickRate { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
||||
|
@ -8,6 +8,7 @@ using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Framework;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -21,6 +22,19 @@ namespace osu.Game.Beatmaps.Formats
|
||||
private LegacySampleBank defaultSampleBank;
|
||||
private int defaultSampleVolume = 100;
|
||||
|
||||
/// <summary>
|
||||
/// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited.
|
||||
/// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
||||
/// </summary>
|
||||
public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
|
||||
/// </summary>
|
||||
public bool ApplyOffsets = true;
|
||||
|
||||
private readonly int offset = UniversalOffset;
|
||||
|
||||
public LegacyBeatmapDecoder()
|
||||
{
|
||||
}
|
||||
@ -28,6 +42,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
public LegacyBeatmapDecoder(string header)
|
||||
{
|
||||
BeatmapVersion = int.Parse(header.Substring(17));
|
||||
|
||||
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
||||
offset += BeatmapVersion < 5 ? 24 : 0;
|
||||
}
|
||||
|
||||
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
|
||||
@ -102,7 +119,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
|
||||
break;
|
||||
case @"PreviewTime":
|
||||
metadata.PreviewTime = int.Parse(pair.Value);
|
||||
metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value));
|
||||
break;
|
||||
case @"Countdown":
|
||||
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
|
||||
@ -232,10 +249,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||
break;
|
||||
case @"SliderMultiplier":
|
||||
difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||
difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||
break;
|
||||
case @"SliderTickRate":
|
||||
difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||
difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -257,8 +274,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
case EventType.Break:
|
||||
var breakEvent = new BreakPeriod
|
||||
{
|
||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
||||
StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)),
|
||||
EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo))
|
||||
};
|
||||
|
||||
if (!breakEvent.HasEffect)
|
||||
@ -273,7 +290,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
string[] split = line.Split(',');
|
||||
|
||||
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo));
|
||||
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||
|
||||
@ -396,7 +413,14 @@ namespace osu.Game.Beatmaps.Formats
|
||||
var obj = parser.Parse(line);
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
obj.StartTime = getOffsetTime(obj.StartTime);
|
||||
beatmap.HitObjects.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
|
||||
|
||||
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount;
|
||||
|
||||
content.Position = Interpolation.ValueAt(Clock.ElapsedFrameTime, content.Position, offset, 0, 1000, Easing.OutQuint);
|
||||
content.Position = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), content.Position, offset, 0, 1000, Easing.OutQuint);
|
||||
content.Scale = new Vector2(1 + ParallaxAmount);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
@ -11,7 +9,5 @@ namespace osu.Game.Graphics.Containers
|
||||
public class ReverseChildIDFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
|
||||
{
|
||||
protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y);
|
||||
|
||||
protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public Action Exit;
|
||||
|
||||
public override bool HandleLeftRightArrows => false;
|
||||
|
||||
private bool focus;
|
||||
public bool HoldFocus
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -56,6 +57,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Full,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = -1,
|
||||
Masking = true
|
||||
};
|
||||
|
||||
public class OsuTabItem : TabItem<T>, IHasAccentColour
|
||||
{
|
||||
protected readonly SpriteText Text;
|
||||
@ -239,5 +248,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OsuTabFillFlowContainer : TabFillFlowContainer
|
||||
{
|
||||
protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
protected virtual bool AllowCommit => false;
|
||||
|
||||
public override bool HandleLeftRightArrows => false;
|
||||
|
||||
public SearchTextBox()
|
||||
{
|
||||
Height = 35;
|
||||
|
@ -1,108 +0,0 @@
|
||||
// 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.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface.Volume
|
||||
{
|
||||
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly Box meterFill;
|
||||
public BindableDouble Bindable { get; } = new BindableDouble();
|
||||
|
||||
public VolumeMeter(string meterName)
|
||||
{
|
||||
Size = new Vector2(40, 180);
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.5f, 0.9f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkGray,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
meterFill = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Scale = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Anchor = Anchor.BottomCentre
|
||||
}
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = meterName,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
};
|
||||
|
||||
Bindable.ValueChanged += delegate { updateFill(); };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateFill();
|
||||
}
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get => Bindable.Value;
|
||||
private set => Bindable.Value = value;
|
||||
}
|
||||
|
||||
public void Increase()
|
||||
{
|
||||
Volume += 0.05f;
|
||||
}
|
||||
|
||||
public void Decrease()
|
||||
{
|
||||
Volume -= 0.05f;
|
||||
}
|
||||
|
||||
private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint);
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!IsHovered) return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.DecreaseVolume:
|
||||
Decrease();
|
||||
return true;
|
||||
case GlobalAction.IncreaseVolume:
|
||||
Increase();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(GlobalAction action) => false;
|
||||
}
|
||||
}
|
@ -199,7 +199,7 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? HttpStatusCode.RequestTimeout;
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics.UserInterface.Volume;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Screens;
|
||||
@ -33,6 +32,7 @@ using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Skinning;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Overlays.Volume;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
@ -75,7 +75,7 @@ namespace osu.Game
|
||||
|
||||
private OsuScreen screenStack;
|
||||
|
||||
private VolumeControl volume;
|
||||
private VolumeOverlay volume;
|
||||
private OnScreenDisplay onscreenDisplay;
|
||||
|
||||
private Bindable<int> configRuleset;
|
||||
@ -232,7 +232,7 @@ namespace osu.Game
|
||||
},
|
||||
}, overlayContent.Add);
|
||||
|
||||
loadComponentSingleFile(volume = new VolumeControl(), Add);
|
||||
loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add);
|
||||
loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add);
|
||||
|
||||
//overlay elements
|
||||
|
@ -53,9 +53,9 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
protected override void AddTabItem(TabItem<Channel> item, bool addToDropdown = true)
|
||||
{
|
||||
if (selectorTab.Depth < float.MaxValue)
|
||||
if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue)
|
||||
// performTabSort might've made selectorTab's position wonky, fix it
|
||||
TabContainer.ChangeChildDepth(selectorTab, float.MaxValue);
|
||||
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
|
||||
|
||||
base.AddTabItem(item, addToDropdown);
|
||||
|
||||
|
@ -101,11 +101,10 @@ namespace osu.Game.Overlays.Music
|
||||
|
||||
public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
items.Add(new PlaylistItem(beatmapSet)
|
||||
{
|
||||
OnSelect = set => OnSelect?.Invoke(set),
|
||||
Depth = items.Count
|
||||
});
|
||||
var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) };
|
||||
|
||||
items.Add(newItem);
|
||||
items.SetLayoutPosition(newItem, items.Count);
|
||||
}
|
||||
|
||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
@ -197,7 +196,7 @@ namespace osu.Game.Overlays.Music
|
||||
{
|
||||
var itemsPos = items.ToLocalSpace(nativeDragPosition);
|
||||
|
||||
int srcIndex = (int)draggedItem.Depth;
|
||||
int srcIndex = (int)items.GetLayoutPosition(draggedItem);
|
||||
|
||||
// Find the last item with position < mouse position. Note we can't directly use
|
||||
// the item positions as they are being transformed
|
||||
@ -219,15 +218,15 @@ namespace osu.Game.Overlays.Music
|
||||
if (srcIndex < dstIndex)
|
||||
{
|
||||
for (int i = srcIndex + 1; i <= dstIndex; i++)
|
||||
items.ChangeChildDepth(items[i], i - 1);
|
||||
items.SetLayoutPosition(items[i], i - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = dstIndex; i < srcIndex; i++)
|
||||
items.ChangeChildDepth(items[i], i + 1);
|
||||
items.SetLayoutPosition(items[i], i + 1);
|
||||
}
|
||||
|
||||
items.ChangeChildDepth(draggedItem, dstIndex);
|
||||
items.SetLayoutPosition(draggedItem, dstIndex);
|
||||
}
|
||||
|
||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||
@ -243,9 +242,6 @@ namespace osu.Game.Overlays.Music
|
||||
}
|
||||
}
|
||||
|
||||
// Compare with reversed ChildID and Depth
|
||||
protected override int Compare(Drawable x, Drawable y) => base.Compare(y, x);
|
||||
|
||||
public IEnumerable<IFilterable> FilterableChildren => Children;
|
||||
|
||||
public ItemSearchContainer()
|
||||
|
@ -129,7 +129,6 @@ namespace osu.Game.Overlays
|
||||
public void Post(Notification notification) => postScheduler.Add(() =>
|
||||
{
|
||||
++runningDepth;
|
||||
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
|
||||
|
||||
notification.Closed += notificationClosed;
|
||||
|
||||
@ -138,7 +137,9 @@ namespace osu.Game.Overlays
|
||||
hasCompletionTarget.CompletionTarget = Post;
|
||||
|
||||
var ourType = notification.GetType();
|
||||
sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)))?.Add(notification);
|
||||
|
||||
var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)));
|
||||
section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth);
|
||||
|
||||
updateCounts();
|
||||
});
|
||||
|
@ -25,10 +25,13 @@ namespace osu.Game.Overlays.Notifications
|
||||
private FlowContainer<Notification> notifications;
|
||||
|
||||
public int DisplayedCount => notifications.Count(n => !n.WasClosed);
|
||||
|
||||
public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read);
|
||||
|
||||
public void Add(Notification notification) => notifications.Add(notification);
|
||||
public void Add(Notification notification, float position)
|
||||
{
|
||||
notifications.Add(notification);
|
||||
notifications.SetLayoutPosition(notification, position);
|
||||
}
|
||||
|
||||
public IEnumerable<Type> AcceptTypes;
|
||||
|
||||
|
@ -40,16 +40,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colour)
|
||||
{
|
||||
RightFlowContainer.Add(new OsuSpriteText
|
||||
var text = new OsuSpriteText
|
||||
{
|
||||
Text = $"accuracy: {Score.Accuracy:P2}",
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Colour = colour.GrayA,
|
||||
TextSize = 11,
|
||||
Font = "Exo2.0-RegularItalic",
|
||||
Depth = -1,
|
||||
});
|
||||
Font = "Exo2.0-RegularItalic"
|
||||
};
|
||||
|
||||
RightFlowContainer.Add(text);
|
||||
RightFlowContainer.SetLayoutPosition(text, 1);
|
||||
|
||||
LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap));
|
||||
LeftFlowContainer.Add(new OsuSpriteText
|
||||
|
@ -45,7 +45,8 @@ namespace osu.Game.Overlays.Settings
|
||||
if (text == null)
|
||||
{
|
||||
// construct lazily for cases where the label is not needed (may be provided by the Control).
|
||||
Add(text = new OsuSpriteText { Depth = 1 });
|
||||
Add(text = new OsuSpriteText());
|
||||
FlowContent.SetLayoutPosition(text, -1);
|
||||
}
|
||||
|
||||
text.Text = value;
|
||||
|
83
osu.Game/Overlays/Volume/MuteButton.cs
Normal file
83
osu.Game/Overlays/Volume/MuteButton.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public class MuteButton : Container, IHasCurrentValue<bool>
|
||||
{
|
||||
public Bindable<bool> Current { get; } = new Bindable<bool>();
|
||||
|
||||
private Color4 hoveredColour, unhoveredColour;
|
||||
private const float width = 100;
|
||||
public const float HEIGHT = 35;
|
||||
|
||||
public MuteButton()
|
||||
{
|
||||
Masking = true;
|
||||
BorderThickness = 3;
|
||||
CornerRadius = HEIGHT / 2;
|
||||
Size = new Vector2(width, HEIGHT);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
hoveredColour = colours.YellowDark;
|
||||
BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f);
|
||||
|
||||
SpriteIcon icon;
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray1,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
}
|
||||
});
|
||||
|
||||
Current.ValueChanged += newValue =>
|
||||
{
|
||||
icon.Icon = newValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up;
|
||||
icon.Margin = new MarginPadding { Left = newValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths
|
||||
};
|
||||
Current.TriggerChange();
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
this.TransformTo<MuteButton, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
this.TransformTo<MuteButton, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(InputState state)
|
||||
{
|
||||
Current.Value = !Current.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface.Volume
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public class VolumeControlReceptor : Container, IKeyBindingHandler<GlobalAction>, IHandleGlobalInput
|
||||
{
|
186
osu.Game/Overlays/Volume/VolumeMeter.cs
Normal file
186
osu.Game/Overlays/Volume/VolumeMeter.cs
Normal file
@ -0,0 +1,186 @@
|
||||
// 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.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Input.Bindings;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private CircularProgress volumeCircle;
|
||||
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 };
|
||||
private readonly float circleSize;
|
||||
private readonly Color4 meterColour;
|
||||
private readonly string name;
|
||||
|
||||
private OsuSpriteText text;
|
||||
private BufferedContainer maxGlow;
|
||||
|
||||
public VolumeMeter(string name, float circleSize, Color4 meterColour)
|
||||
{
|
||||
this.circleSize = circleSize;
|
||||
this.meterColour = meterColour;
|
||||
this.name = name;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Add(new Container
|
||||
{
|
||||
Size = new Vector2(120, 20),
|
||||
CornerRadius = 10,
|
||||
Masking = true,
|
||||
Margin = new MarginPadding { Left = circleSize + 10 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray1,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = "Exo2.0-Bold",
|
||||
Text = name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CircularProgress bgProgress;
|
||||
|
||||
Add(new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
Size = new Vector2(circleSize),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray1,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
bgProgress = new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
InnerRadius = 0.05f,
|
||||
Rotation = 180,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colours.Gray2,
|
||||
Size = new Vector2(0.8f)
|
||||
},
|
||||
(volumeCircle = new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
InnerRadius = 0.05f,
|
||||
Rotation = 180,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.8f)
|
||||
}).WithEffect(new GlowEffect
|
||||
{
|
||||
Colour = meterColour,
|
||||
Strength = 2
|
||||
}),
|
||||
maxGlow = (text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = "Venera",
|
||||
TextSize = 0.16f * circleSize
|
||||
}).WithEffect(new GlowEffect
|
||||
{
|
||||
Colour = Color4.Transparent,
|
||||
PadExtent = true,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
Bindable.ValueChanged += newVolume => { this.TransformTo("DisplayVolume", newVolume, 400, Easing.OutQuint); };
|
||||
bgProgress.Current.Value = 0.75f;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Bindable.TriggerChange();
|
||||
}
|
||||
|
||||
private double displayVolume;
|
||||
|
||||
protected double DisplayVolume
|
||||
{
|
||||
get => displayVolume;
|
||||
set
|
||||
{
|
||||
displayVolume = value;
|
||||
|
||||
if (displayVolume > 0.99f)
|
||||
{
|
||||
text.Text = "MAX";
|
||||
maxGlow.EffectColour = meterColour.Opacity(2f);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxGlow.EffectColour = Color4.Transparent;
|
||||
text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
volumeCircle.Current.Value = displayVolume * 0.75f;
|
||||
}
|
||||
}
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get => Bindable;
|
||||
private set => Bindable.Value = value;
|
||||
}
|
||||
|
||||
public void Increase() => Volume += 0.05f;
|
||||
|
||||
public void Decrease() => Volume -= 0.05f;
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!IsHovered) return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.DecreaseVolume:
|
||||
Decrease();
|
||||
return true;
|
||||
case GlobalAction.IncreaseVolume:
|
||||
Increase();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(GlobalAction action) => false;
|
||||
}
|
||||
}
|
@ -1,57 +1,84 @@
|
||||
// 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.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using OpenTK;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface.Volume
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class VolumeControl : OverlayContainer
|
||||
public class VolumeOverlay : OverlayContainer
|
||||
{
|
||||
private readonly VolumeMeter volumeMeterMaster;
|
||||
private readonly IconButton muteIcon;
|
||||
private const float offset = 10;
|
||||
|
||||
private VolumeMeter volumeMeterMaster;
|
||||
private VolumeMeter volumeMeterEffect;
|
||||
private VolumeMeter volumeMeterMusic;
|
||||
private MuteButton muteButton;
|
||||
|
||||
protected override bool BlockPassThroughMouse => false;
|
||||
|
||||
public VolumeControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.BottomRight;
|
||||
Origin = Anchor.BottomRight;
|
||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||
|
||||
Children = new Drawable[]
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 300,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding { Left = 10, Right = 10, Top = 30, Bottom = 30 },
|
||||
Spacing = new Vector2(15, 0),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Spacing = new Vector2(0, offset),
|
||||
Margin = new MarginPadding { Left = offset },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker)
|
||||
{
|
||||
Size = new Vector2(IconButton.BUTTON_SIZE),
|
||||
Child = muteIcon = new IconButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.fa_volume_up,
|
||||
Action = () => Adjust(GlobalAction.ToggleMute),
|
||||
}
|
||||
Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } //to counter the mute button and re-center the volume meters
|
||||
},
|
||||
volumeMeterMaster = new VolumeMeter("Master"),
|
||||
volumeMeterEffect = new VolumeMeter("Effects"),
|
||||
volumeMeterMusic = new VolumeMeter("Music")
|
||||
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
||||
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
||||
muteButton = new MuteButton
|
||||
{
|
||||
Margin = new MarginPadding { Top = 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
|
||||
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
|
||||
|
||||
muteButton.Current.ValueChanged += mute =>
|
||||
{
|
||||
if (mute)
|
||||
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
else
|
||||
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
};
|
||||
}
|
||||
|
||||
@ -62,7 +89,13 @@ namespace osu.Game.Graphics.UserInterface.Volume
|
||||
volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged();
|
||||
volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged();
|
||||
volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged();
|
||||
muted.ValueChanged += _ => settingChanged();
|
||||
muteButton.Current.ValueChanged += _ => settingChanged();
|
||||
}
|
||||
|
||||
private void settingChanged()
|
||||
{
|
||||
Show();
|
||||
schedulePopOut();
|
||||
}
|
||||
|
||||
public bool Adjust(GlobalAction action)
|
||||
@ -83,50 +116,15 @@ namespace osu.Game.Graphics.UserInterface.Volume
|
||||
return true;
|
||||
case GlobalAction.ToggleMute:
|
||||
Show();
|
||||
muted.Toggle();
|
||||
muteButton.Current.Value = !muteButton.Current;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void settingChanged()
|
||||
{
|
||||
Show();
|
||||
schedulePopOut();
|
||||
}
|
||||
|
||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||
|
||||
private readonly BindableBool muted = new BindableBool();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
|
||||
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
|
||||
|
||||
muted.ValueChanged += mute =>
|
||||
{
|
||||
if (mute)
|
||||
{
|
||||
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
muteIcon.Icon = FontAwesome.fa_volume_off;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
muteIcon.Icon = FontAwesome.fa_volume_up;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ScheduledDelegate popOutDelegate;
|
||||
|
||||
private readonly VolumeMeter volumeMeterEffect;
|
||||
private readonly VolumeMeter volumeMeterMusic;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
ClearTransforms();
|
@ -35,8 +35,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
|
||||
|
||||
private List<DrawableHitObject> nestedHitObjects;
|
||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
||||
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
|
||||
public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated;
|
||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.Value;
|
||||
|
||||
public event Action<DrawableHitObject, Judgement> OnJudgement;
|
||||
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
|
||||
@ -52,12 +53,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit.
|
||||
/// </summary>
|
||||
public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (NestedHitObjects?.All(n => n.IsHit) ?? true);
|
||||
public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit));
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
|
||||
/// </summary>
|
||||
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
|
||||
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged));
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableHitObject"/> can be judged.
|
||||
@ -160,14 +161,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
protected virtual void AddNested(DrawableHitObject h)
|
||||
{
|
||||
if (nestedHitObjects == null)
|
||||
nestedHitObjects = new List<DrawableHitObject>();
|
||||
|
||||
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
|
||||
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
|
||||
h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
|
||||
|
||||
nestedHitObjects.Add(h);
|
||||
nestedHitObjects.Value.Add(h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -211,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
if (AllJudged)
|
||||
return false;
|
||||
|
||||
if (NestedHitObjects != null)
|
||||
if (HasNestedHitObjects)
|
||||
foreach (var d in NestedHitObjects)
|
||||
judgementOccurred |= d.UpdateJudgement(userTriggered);
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Input.Handlers;
|
||||
using OpenTK;
|
||||
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.
|
||||
/// It handles logic of any frames which *must* be executed.
|
||||
/// </summary>
|
||||
public abstract class FramedReplayInputHandler : ReplayInputHandler
|
||||
public abstract class FramedReplayInputHandler<TFrame> : ReplayInputHandler
|
||||
where TFrame : ReplayFrame
|
||||
{
|
||||
private readonly Replay replay;
|
||||
|
||||
protected List<ReplayFrame> Frames => replay.Frames;
|
||||
|
||||
public ReplayFrame CurrentFrame => !hasFrames ? null : Frames[currentFrameIndex];
|
||||
public ReplayFrame NextFrame => !hasFrames ? null : Frames[nextFrameIndex];
|
||||
public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex];
|
||||
public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex];
|
||||
|
||||
private int currentFrameIndex;
|
||||
|
||||
@ -46,31 +46,14 @@ namespace osu.Game.Rulesets.Replays
|
||||
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 bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
|
||||
public bool AtFirstFrame => currentFrameIndex == 0;
|
||||
|
||||
public Vector2 Size => new Vector2(512, 384);
|
||||
|
||||
private const double sixty_frame_time = 1000.0 / 60;
|
||||
|
||||
private double currentTime;
|
||||
protected double CurrentTime { get; private set; }
|
||||
private int currentDirection;
|
||||
|
||||
/// <summary>
|
||||
@ -79,14 +62,16 @@ namespace osu.Game.Rulesets.Replays
|
||||
/// </summary>
|
||||
public bool FrameAccuratePlayback = true;
|
||||
|
||||
private bool hasFrames => Frames.Count > 0;
|
||||
protected bool HasFrames => Frames.Count > 0;
|
||||
|
||||
private bool inImportantSection =>
|
||||
FrameAccuratePlayback &&
|
||||
HasFrames && FrameAccuratePlayback &&
|
||||
//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
|
||||
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>
|
||||
/// 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>
|
||||
public override double? SetFrameFromTime(double time)
|
||||
{
|
||||
currentDirection = time.CompareTo(currentTime);
|
||||
currentDirection = time.CompareTo(CurrentTime);
|
||||
if (currentDirection == 0) currentDirection = 1;
|
||||
|
||||
if (hasFrames)
|
||||
if (HasFrames)
|
||||
{
|
||||
// check if the next frame is in the "future" for the current playback direction
|
||||
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
|
||||
// that would occur as a result of this frame in forward playback
|
||||
if (currentDirection == -1)
|
||||
return currentTime = CurrentFrame.Time - 1;
|
||||
return currentTime = CurrentFrame.Time;
|
||||
return CurrentTime = CurrentFrame.Time - 1;
|
||||
return CurrentTime = CurrentFrame.Time;
|
||||
}
|
||||
}
|
||||
|
||||
return currentTime = time;
|
||||
return CurrentTime = time;
|
||||
}
|
||||
|
||||
protected class ReplayMouseState : MouseState
|
||||
|
38
osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs
Normal file
38
osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
namespace osu.Game.Rulesets.Replays.Legacy
|
||||
{
|
||||
[Flags]
|
||||
public enum ReplayButtonState
|
@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Replays
|
||||
public class Replay
|
||||
{
|
||||
public User User;
|
||||
|
||||
public List<ReplayFrame> Frames = new List<ReplayFrame>();
|
||||
}
|
||||
}
|
||||
|
@ -1,70 +1,19 @@
|
||||
// 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
|
||||
{
|
||||
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 ReplayButtonState ButtonState;
|
||||
|
||||
protected ReplayFrame()
|
||||
public ReplayFrame()
|
||||
{
|
||||
}
|
||||
|
||||
public ReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState)
|
||||
public ReplayFrame(double time)
|
||||
{
|
||||
MouseX = mouseX;
|
||||
MouseY = mouseY;
|
||||
ButtonState = buttonState;
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
Normal file
22
osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
@ -63,7 +64,7 @@ namespace osu.Game.Rulesets
|
||||
/// <summary>
|
||||
/// Do not override this unless you are a legacy mode.
|
||||
/// </summary>
|
||||
public virtual int LegacyID => -1;
|
||||
public virtual int? LegacyID => null;
|
||||
|
||||
/// <summary>
|
||||
/// A unique short name to reference this ruleset in online requests.
|
||||
@ -89,6 +90,13 @@ namespace osu.Game.Rulesets
|
||||
/// <returns>A descriptive name of the variant.</returns>
|
||||
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>
|
||||
/// Create a ruleset info based on this ruleset.
|
||||
/// </summary>
|
||||
|
152
osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
Normal file
152
osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,16 +2,12 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Users;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
@ -53,127 +49,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
public Score ReadReplayFile(string replayFilename)
|
||||
{
|
||||
Score score;
|
||||
|
||||
using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
|
||||
using (SerializationReader sr = new SerializationReader(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;
|
||||
return new LegacyScoreParser(rulesets, beatmaps).Parse(s);
|
||||
}
|
||||
|
||||
/// <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 };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@ -110,7 +111,7 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <returns>The input manager.</returns>
|
||||
public abstract PassThroughInputManager CreateInputManager();
|
||||
|
||||
protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
|
||||
protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
|
||||
|
||||
public Replay Replay { get; private set; }
|
||||
|
||||
|
@ -91,8 +91,6 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
#region Clock control
|
||||
|
||||
protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves
|
||||
|
||||
private ManualClock clock;
|
||||
private IFrameBasedClock parentClock;
|
||||
|
||||
@ -103,6 +101,7 @@ namespace osu.Game.Rulesets.UI
|
||||
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
|
||||
parentClock = Clock;
|
||||
|
||||
ProcessCustomClock = false;
|
||||
Clock = new FramedClock(clock = new ManualClock
|
||||
{
|
||||
CurrentTime = parentClock.CurrentTime,
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
|
||||
var controlPoint = controlPointAt(obj.HitObject.StartTime);
|
||||
obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier;
|
||||
|
||||
if (obj.NestedHitObjects != null)
|
||||
if (obj.HasNestedHitObjects)
|
||||
{
|
||||
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
|
||||
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.NestedHitObjects != null)
|
||||
if (obj.HasNestedHitObjects)
|
||||
{
|
||||
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
|
||||
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
|
||||
|
@ -4,8 +4,6 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using OpenTK;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
@ -22,8 +20,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y);
|
||||
|
||||
protected override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Reverse();
|
||||
|
||||
public override Anchor Origin => Anchor.Custom;
|
||||
|
||||
public override Vector2 OriginPosition
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private static bool hasShownNotificationOnce;
|
||||
|
||||
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock)
|
||||
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play
|
||||
BindRulesetContainer(rulesetContainer);
|
||||
|
||||
Progress.Objects = rulesetContainer.Objects;
|
||||
Progress.AudioClock = decoupledClock;
|
||||
Progress.AudioClock = offsetClock;
|
||||
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded;
|
||||
Progress.OnSeek = pos => decoupledClock.Seek(pos);
|
||||
Progress.OnSeek = pos => adjustableClock.Seek(pos);
|
||||
|
||||
ModDisplay.Current.BindTo(working.Mods);
|
||||
|
||||
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock;
|
||||
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
|
@ -44,14 +44,22 @@ namespace osu.Game.Screens.Play
|
||||
public Action OnResume;
|
||||
public Action OnPause;
|
||||
|
||||
public IAdjustableClock AudioClock;
|
||||
public FramedClock FramedClock;
|
||||
private readonly IAdjustableClock adjustableClock;
|
||||
private readonly FramedClock framedClock;
|
||||
|
||||
public PauseContainer()
|
||||
public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock)
|
||||
{
|
||||
this.framedClock = framedClock;
|
||||
this.adjustableClock = adjustableClock;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddInternal(content = new Container { RelativeSizeAxes = Axes.Both });
|
||||
AddInternal(content = new Container
|
||||
{
|
||||
Clock = this.framedClock,
|
||||
ProcessCustomClock = false,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
});
|
||||
|
||||
AddInternal(pauseOverlay = new PauseOverlay
|
||||
{
|
||||
@ -65,47 +73,37 @@ namespace osu.Game.Screens.Play
|
||||
});
|
||||
}
|
||||
|
||||
public void Pause(bool force = false)
|
||||
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
|
||||
{
|
||||
if (!CanPause && !force) return;
|
||||
|
||||
if (IsPaused) return;
|
||||
|
||||
// stop the decoupled clock (stops the audio eventually)
|
||||
AudioClock.Stop();
|
||||
|
||||
// stop processing updatess on the offset clock (instantly freezes time for all our components)
|
||||
FramedClock.ProcessSourceClockFrames = false;
|
||||
|
||||
// stop the seekable clock (stops the audio eventually)
|
||||
adjustableClock.Stop();
|
||||
IsPaused = true;
|
||||
|
||||
// we need to do a final check after all of our children have processed up to the paused clock time.
|
||||
// this is to cover cases where, for instance, the player fails in the current processing frame.
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!CanPause) return;
|
||||
OnPause?.Invoke();
|
||||
pauseOverlay.Show();
|
||||
|
||||
lastPauseActionTime = Time.Current;
|
||||
|
||||
OnPause?.Invoke();
|
||||
pauseOverlay.Show();
|
||||
});
|
||||
}
|
||||
lastPauseActionTime = Time.Current;
|
||||
});
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (!IsPaused) return;
|
||||
|
||||
IsPaused = false;
|
||||
FramedClock.ProcessSourceClockFrames = true;
|
||||
|
||||
IsResuming = false;
|
||||
lastPauseActionTime = Time.Current;
|
||||
|
||||
OnResume?.Invoke();
|
||||
// seek back to the time of the framed clock.
|
||||
// this accounts for the audio clock potentially taking time to enter a completely stopped state.
|
||||
adjustableClock.Seek(framedClock.CurrentTime);
|
||||
adjustableClock.Start();
|
||||
|
||||
OnResume?.Invoke();
|
||||
pauseOverlay.Hide();
|
||||
AudioClock.Start();
|
||||
IsResuming = false;
|
||||
}
|
||||
|
||||
private OsuGameBase game;
|
||||
@ -122,6 +120,9 @@ namespace osu.Game.Screens.Play
|
||||
if (!game.IsActive && CanPause)
|
||||
Pause();
|
||||
|
||||
if (!IsPaused)
|
||||
framedClock.ProcessFrame();
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
|
@ -55,9 +55,12 @@ namespace osu.Game.Screens.Play
|
||||
public CursorContainer Cursor => RulesetContainer.Cursor;
|
||||
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
|
||||
|
||||
private IAdjustableClock adjustableSourceClock;
|
||||
private FramedOffsetClock offsetClock;
|
||||
private DecoupleableInterpolatingFramedClock decoupledClock;
|
||||
private IAdjustableClock sourceClock;
|
||||
|
||||
/// <summary>
|
||||
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
|
||||
/// </summary>
|
||||
private DecoupleableInterpolatingFramedClock adjustableClock;
|
||||
|
||||
private PauseContainer pauseContainer;
|
||||
|
||||
@ -139,17 +142,18 @@ namespace osu.Game.Screens.Play
|
||||
return;
|
||||
}
|
||||
|
||||
adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
||||
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
||||
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||
|
||||
var firstObjectTime = RulesetContainer.Objects.First().StartTime;
|
||||
decoupledClock.Seek(AllowLeadIn
|
||||
adjustableClock.Seek(AllowLeadIn
|
||||
? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))
|
||||
: firstObjectTime);
|
||||
|
||||
decoupledClock.ProcessFrame();
|
||||
adjustableClock.ProcessFrame();
|
||||
|
||||
offsetClock = new FramedOffsetClock(decoupledClock);
|
||||
// the final usable gameplay clock with user-set offsets applied.
|
||||
var offsetClock = new FramedOffsetClock(adjustableClock);
|
||||
|
||||
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
|
||||
@ -159,16 +163,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
storyboardContainer = new Container
|
||||
pauseContainer = new PauseContainer(offsetClock, adjustableClock)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Clock = offsetClock,
|
||||
Alpha = 0,
|
||||
},
|
||||
pauseContainer = new PauseContainer
|
||||
{
|
||||
AudioClock = decoupledClock,
|
||||
FramedClock = offsetClock,
|
||||
OnRetry = Restart,
|
||||
OnQuit = Exit,
|
||||
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
|
||||
@ -180,15 +176,23 @@ namespace osu.Game.Screens.Play
|
||||
OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
storyboardContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Clock = offsetClock,
|
||||
Child = RulesetContainer,
|
||||
Alpha = 0,
|
||||
},
|
||||
new SkipButton(firstObjectTime) { AudioClock = decoupledClock },
|
||||
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock)
|
||||
RulesetContainer,
|
||||
new SkipButton(firstObjectTime)
|
||||
{
|
||||
Clock = Clock, // skip button doesn't want to use the audio clock directly
|
||||
ProcessCustomClock = false,
|
||||
AdjustableClock = adjustableClock,
|
||||
FramedClock = offsetClock,
|
||||
},
|
||||
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
|
||||
{
|
||||
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
|
||||
ProcessCustomClock = false,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
@ -196,7 +200,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Clock = decoupledClock,
|
||||
ProcessCustomClock = false,
|
||||
Breaks = beatmap.Breaks
|
||||
}
|
||||
}
|
||||
@ -233,11 +237,11 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private void applyRateFromMods()
|
||||
{
|
||||
if (adjustableSourceClock == null) return;
|
||||
if (sourceClock == null) return;
|
||||
|
||||
adjustableSourceClock.Rate = 1;
|
||||
sourceClock.Rate = 1;
|
||||
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
|
||||
mod.ApplyToClock(adjustableSourceClock);
|
||||
mod.ApplyToClock(sourceClock);
|
||||
}
|
||||
|
||||
private void initializeStoryboard(bool asyncLoad)
|
||||
@ -296,7 +300,7 @@ namespace osu.Game.Screens.Play
|
||||
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
||||
return false;
|
||||
|
||||
decoupledClock.Stop();
|
||||
adjustableClock.Stop();
|
||||
|
||||
HasFailed = true;
|
||||
failOverlay.Retries = RestartCount;
|
||||
@ -325,17 +329,19 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
adjustableSourceClock.Reset();
|
||||
sourceClock.Reset();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
decoupledClock.ChangeSource(adjustableSourceClock);
|
||||
adjustableClock.ChangeSource(sourceClock);
|
||||
applyRateFromMods();
|
||||
|
||||
this.Delay(750).Schedule(() =>
|
||||
{
|
||||
if (!pauseContainer.IsPaused)
|
||||
decoupledClock.Start();
|
||||
{
|
||||
adjustableClock.Start();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -362,9 +368,7 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
if (loadedSuccessfully)
|
||||
{
|
||||
pauseContainer?.Pause();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play
|
||||
public class SkipButton : OverlayContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly double startTime;
|
||||
public IAdjustableClock AudioClock;
|
||||
|
||||
public IAdjustableClock AdjustableClock;
|
||||
public IFrameBasedClock FramedClock;
|
||||
|
||||
private Button button;
|
||||
private Box remainingTimeBox;
|
||||
@ -60,8 +62,11 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
var baseClock = Clock;
|
||||
|
||||
if (AudioClock != null)
|
||||
Clock = new FramedClock(AudioClock) { ProcessSourceClockFrames = false };
|
||||
if (FramedClock != null)
|
||||
{
|
||||
Clock = FramedClock;
|
||||
ProcessCustomClock = false;
|
||||
}
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -109,7 +114,7 @@ namespace osu.Game.Screens.Play
|
||||
using (BeginAbsoluteSequence(beginFadeTime))
|
||||
this.FadeOut(fade_time);
|
||||
|
||||
button.Action = () => AudioClock?.Seek(startTime - skip_required_cutoff - fade_time);
|
||||
button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time);
|
||||
|
||||
displayTime = Time.Current;
|
||||
|
||||
|
@ -41,19 +41,25 @@ namespace osu.Game.Screens.Select
|
||||
/// <para>Higher depth to be put on the left, and lower to be put on the right.</para>
|
||||
/// <para>Notice this is different to <see cref="Options.BeatmapOptionsOverlay"/>!</para>
|
||||
/// </param>
|
||||
public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) => buttons.Add(new FooterButton
|
||||
public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0)
|
||||
{
|
||||
Text = text,
|
||||
Height = play_song_select_button_height,
|
||||
Width = play_song_select_button_width,
|
||||
Depth = depth,
|
||||
SelectedColour = colour,
|
||||
DeselectedColour = colour.Opacity(0.5f),
|
||||
Hotkey = hotkey,
|
||||
Hovered = updateModeLight,
|
||||
HoverLost = updateModeLight,
|
||||
Action = action,
|
||||
});
|
||||
var button = new FooterButton
|
||||
{
|
||||
Text = text,
|
||||
Height = play_song_select_button_height,
|
||||
Width = play_song_select_button_width,
|
||||
Depth = depth,
|
||||
SelectedColour = colour,
|
||||
DeselectedColour = colour.Opacity(0.5f),
|
||||
Hotkey = hotkey,
|
||||
Hovered = updateModeLight,
|
||||
HoverLost = updateModeLight,
|
||||
Action = action,
|
||||
};
|
||||
|
||||
buttons.Add(button);
|
||||
buttons.SetLayoutPosition(button, -depth);
|
||||
}
|
||||
|
||||
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select.Options
|
||||
/// </param>
|
||||
public void AddButton(string firstLine, string secondLine, FontAwesome icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0)
|
||||
{
|
||||
buttonsContainer.Add(new BeatmapOptionsButton
|
||||
var button = new BeatmapOptionsButton
|
||||
{
|
||||
FirstLineText = firstLine,
|
||||
SecondLineText = secondLine,
|
||||
@ -108,7 +108,10 @@ namespace osu.Game.Screens.Select.Options
|
||||
action?.Invoke();
|
||||
},
|
||||
HotKey = hotkey
|
||||
});
|
||||
};
|
||||
|
||||
buttonsContainer.Add(button);
|
||||
buttonsContainer.SetLayoutPosition(button, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,16 +22,14 @@ namespace osu.Game.Skinning
|
||||
public LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager)
|
||||
: base(skin)
|
||||
{
|
||||
storage = new LegacySkinResourceStore(skin, storage);
|
||||
samples = audioManager.GetSampleManager(storage);
|
||||
textures = new TextureStore(new RawTextureLoaderStore(storage));
|
||||
}
|
||||
|
||||
private string getPathForFile(string filename) =>
|
||||
SkinInfo.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
public override Drawable GetDrawableComponent(string componentName)
|
||||
{
|
||||
var texture = textures.Get(getPathForFile(componentName.Split('/').Last()));
|
||||
var texture = textures.Get(componentName);
|
||||
if (texture == null) return null;
|
||||
|
||||
return new Sprite
|
||||
@ -42,6 +40,25 @@ namespace osu.Game.Skinning
|
||||
};
|
||||
}
|
||||
|
||||
public override SampleChannel GetSample(string sampleName) => samples.Get(getPathForFile(sampleName.Split('/').Last()));
|
||||
public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName);
|
||||
|
||||
private class LegacySkinResourceStore : IResourceStore<byte[]>
|
||||
{
|
||||
private readonly SkinInfo skin;
|
||||
private readonly IResourceStore<byte[]> underlyingStore;
|
||||
|
||||
private string getPathForFile(string filename) =>
|
||||
skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
public LegacySkinResourceStore(SkinInfo skin, IResourceStore<byte[]> underlyingStore)
|
||||
{
|
||||
this.skin = skin;
|
||||
this.underlyingStore = underlyingStore;
|
||||
}
|
||||
|
||||
public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name));
|
||||
|
||||
byte[] IResourceStore<byte[]>.Get(string name) => underlyingStore.Get(getPathForFile(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
public bool FlipH { get; set; }
|
||||
public bool FlipV { get; set; }
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
protected override Vector2 DrawScale
|
||||
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);
|
||||
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
public bool FlipH { get; set; }
|
||||
public bool FlipV { get; set; }
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
protected override Vector2 DrawScale
|
||||
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);
|
||||
|
||||
|
@ -109,10 +109,13 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
private Beatmap getBeatmap(string name)
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
var decoder = Decoder.GetDecoder(stream);
|
||||
((LegacyBeatmapDecoder)decoder).ApplyOffsets = false;
|
||||
return decoder.DecodeBeatmap(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private Stream openResource(string name)
|
||||
|
@ -351,6 +351,10 @@
|
||||
<Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" />
|
||||
<Compile Include="Overlays\Settings\Sections\Gameplay\ScrollingSettings.cs" />
|
||||
<Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" />
|
||||
<Compile Include="Overlays\VolumeOverlay.cs" />
|
||||
<Compile Include="Overlays\Volume\MuteButton.cs" />
|
||||
<Compile Include="Overlays\Volume\VolumeControlReceptor.cs" />
|
||||
<Compile Include="Overlays\Volume\VolumeMeter.cs" />
|
||||
<Compile Include="Rulesets\Configuration\IRulesetConfigManager.cs" />
|
||||
<Compile Include="Rulesets\Configuration\RulesetConfigManager.cs" />
|
||||
<Compile Include="Rulesets\Edit\Layers\BorderLayer.cs" />
|
||||
@ -365,6 +369,11 @@
|
||||
<Compile Include="Overlays\Social\SocialPanel.cs" />
|
||||
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.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="Screens\Play\PlayerSettings\VisualSettings.cs" />
|
||||
<Compile Include="Rulesets\Objects\CatmullApproximator.cs" />
|
||||
@ -469,9 +478,6 @@
|
||||
<Compile Include="Graphics\UserInterface\SimpleComboCounter.cs" />
|
||||
<Compile Include="Graphics\UserInterface\StarCounter.cs" />
|
||||
<Compile Include="Graphics\UserInterface\TwoLayerButton.cs" />
|
||||
<Compile Include="Graphics\UserInterface\Volume\VolumeControl.cs" />
|
||||
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
|
||||
<Compile Include="Graphics\UserInterface\Volume\VolumeMeter.cs" />
|
||||
<Compile Include="Input\Bindings\DatabasedKeyBinding.cs" />
|
||||
<Compile Include="Input\Bindings\DatabasedKeyBindingContainer.cs" />
|
||||
<Compile Include="Input\Bindings\GlobalActionContainer.cs" />
|
||||
@ -710,8 +716,6 @@
|
||||
<Compile Include="Rulesets\Replays\FramedReplayInputHandler.cs" />
|
||||
<Compile Include="Rulesets\Replays\IAutoGenerator.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\RulesetInfo.cs" />
|
||||
<Compile Include="Rulesets\RulesetStore.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user