mirror of
https://github.com/ppy/osu.git
synced 2024-12-16 02:15:17 +08:00
Merge branch 'master' into fix-spinner-rpm-user-rate-adjust
This commit is contained in:
commit
fd58a24183
@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
// todo: add support for HT DT
|
// todo: add support for HT DT
|
||||||
const double dash_speed = Catcher.BASE_SPEED;
|
const double dash_speed = Catcher.BASE_SPEED;
|
||||||
const double movement_speed = dash_speed / 2;
|
const double movement_speed = dash_speed / 2;
|
||||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
||||||
|
|
||||||
var actions = new List<ManiaAction>();
|
var actions = new List<ManiaAction>();
|
||||||
|
@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double editor_hit_object_fade_out_extension = 500;
|
private const double editor_hit_object_fade_out_extension = 700;
|
||||||
|
|
||||||
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
@ -32,11 +33,30 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
switch (state)
|
if (state == ArmedState.Idle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||||
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case ArmedState.Miss:
|
default:
|
||||||
|
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
|
||||||
|
return;
|
||||||
|
|
||||||
|
case DrawableSlider _:
|
||||||
|
// no specifics to sliders but let them fade slower below.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableHitCircle circle: // also handles slider heads
|
||||||
|
circle.ApproachCircle
|
||||||
|
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||||
|
.Expire();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the existing fade out transform
|
// Get the existing fade out transform
|
||||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -44,8 +64,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
|
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
|
||||||
|
@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
buttonIndex = 0;
|
buttonIndex = 0;
|
||||||
|
|
||||||
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
|
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
|
||||||
|
{
|
||||||
|
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
||||||
|
protected Container PlayfieldContainer { get; private set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
|
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty(),
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = @"Unknown",
|
||||||
|
Title = @"Sample Beatmap",
|
||||||
|
AuthorString = @"peppy",
|
||||||
|
},
|
||||||
|
Ruleset = new TaikoRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(PlayfieldContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 768,
|
||||||
|
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,26 +2,36 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
internal class DrawableTestHit : DrawableTaikoHitObject
|
public class DrawableTestHit : DrawableHit
|
||||||
{
|
{
|
||||||
private readonly HitResult type;
|
public readonly HitResult Type;
|
||||||
|
|
||||||
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
|
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
|
||||||
: base(hit)
|
: base(hit)
|
||||||
{
|
{
|
||||||
this.type = type;
|
Type = type;
|
||||||
|
|
||||||
|
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
// base implementation in DrawableHitObject forces alpha to 1.
|
||||||
|
// suppress locally to allow hiding the visuals wherever necessary.
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Result.Type = type;
|
Result.Type = Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnPressed(TaikoAction action) => false;
|
public override bool OnPressed(TaikoAction action) => false;
|
||||||
|
@ -2,17 +2,14 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
public class DrawableTestStrongHit : DrawableHit
|
public class DrawableTestStrongHit : DrawableTestHit
|
||||||
{
|
{
|
||||||
private readonly HitResult type;
|
|
||||||
private readonly bool hitBoth;
|
private readonly bool hitBoth;
|
||||||
|
|
||||||
public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true)
|
public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true)
|
||||||
@ -20,12 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
IsStrong = true,
|
IsStrong = true,
|
||||||
StartTime = startTime,
|
StartTime = startTime,
|
||||||
})
|
}, type)
|
||||||
{
|
{
|
||||||
// in order to create nested strong hit
|
|
||||||
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
|
||||||
|
|
||||||
this.type = type;
|
|
||||||
this.hitBoth = hitBoth;
|
this.hitBoth = hitBoth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
base.LoadAsyncComplete();
|
base.LoadAsyncComplete();
|
||||||
|
|
||||||
Result.Type = type;
|
|
||||||
|
|
||||||
var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single();
|
var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single();
|
||||||
nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss;
|
nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnPressed(TaikoAction action) => false;
|
public override bool OnPressed(TaikoAction action) => false;
|
||||||
|
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
foreach (var playfield in playfields)
|
foreach (var playfield in playfields)
|
||||||
{
|
{
|
||||||
var hit = new DrawableTestHit(new Hit(), judgementResult.Type);
|
var hit = new DrawableTestHit(new Hit(), judgementResult.Type);
|
||||||
Add(hit);
|
playfield.Add(hit);
|
||||||
|
|
||||||
playfield.OnNewResult(hit, judgementResult);
|
playfield.OnNewResult(hit, judgementResult);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
@ -29,15 +28,17 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth))));
|
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getContentFor(DrawableTaikoHitObject hit)
|
private Drawable getContentFor(DrawableTestHit hit)
|
||||||
{
|
{
|
||||||
return new Container
|
return new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
hit,
|
// the hit needs to be added to hierarchy in order for nested objects to be created correctly.
|
||||||
new HitExplosion(hit)
|
// setting zero alpha is supposed to prevent the test from looking broken.
|
||||||
|
hit.With(h => h.Alpha = 0),
|
||||||
|
new HitExplosion(hit, hit.Type)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -46,9 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
|
private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
|
||||||
|
|
||||||
private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth)
|
private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth);
|
||||||
=> new DrawableTestStrongHit(Time.Current, type, hitBoth);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs
Normal file
48
osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Judgements;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneFlyingHits : DrawableTaikoRulesetTestScene
|
||||||
|
{
|
||||||
|
[TestCase(HitType.Centre)]
|
||||||
|
[TestCase(HitType.Rim)]
|
||||||
|
public void TestFlyingHits(HitType hitType)
|
||||||
|
{
|
||||||
|
DrawableFlyingHit flyingHit = null;
|
||||||
|
|
||||||
|
AddStep("add flying hit", () =>
|
||||||
|
{
|
||||||
|
addFlyingHit(hitType);
|
||||||
|
|
||||||
|
// flying hits all land in one common scrolling container (and stay there for rewind purposes),
|
||||||
|
// so we need to manually get the latest one.
|
||||||
|
flyingHit = this.ChildrenOfType<DrawableFlyingHit>()
|
||||||
|
.OrderByDescending(h => h.HitObject.StartTime)
|
||||||
|
.FirstOrDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFlyingHit(HitType hitType)
|
||||||
|
{
|
||||||
|
var tick = new DrumRollTick { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current };
|
||||||
|
|
||||||
|
DrawableDrumRollTick h;
|
||||||
|
DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType });
|
||||||
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -18,13 +16,12 @@ using osu.Game.Rulesets.Taiko.Judgements;
|
|||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHits : OsuTestScene
|
public class TestSceneHits : DrawableTaikoRulesetTestScene
|
||||||
{
|
{
|
||||||
private const double default_duration = 3000;
|
private const double default_duration = 3000;
|
||||||
private const float scroll_time = 1000;
|
private const float scroll_time = 1000;
|
||||||
@ -32,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
protected override double TimePerAction => default_duration * 2;
|
protected override double TimePerAction => default_duration * 2;
|
||||||
|
|
||||||
private readonly Random rng = new Random(1337);
|
private readonly Random rng = new Random(1337);
|
||||||
private DrawableTaikoRuleset drawableRuleset;
|
|
||||||
private Container playfieldContainer;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -64,35 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
AddStep("Height test 4", () => changePlayfieldSize(4));
|
AddStep("Height test 4", () => changePlayfieldSize(4));
|
||||||
AddStep("Height test 5", () => changePlayfieldSize(5));
|
AddStep("Height test 5", () => changePlayfieldSize(5));
|
||||||
AddStep("Reset height", () => changePlayfieldSize(6));
|
AddStep("Reset height", () => changePlayfieldSize(6));
|
||||||
|
|
||||||
var controlPointInfo = new ControlPointInfo();
|
|
||||||
controlPointInfo.Add(0, new TimingControlPoint());
|
|
||||||
|
|
||||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
|
||||||
{
|
|
||||||
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
|
||||||
BeatmapInfo = new BeatmapInfo
|
|
||||||
{
|
|
||||||
BaseDifficulty = new BeatmapDifficulty(),
|
|
||||||
Metadata = new BeatmapMetadata
|
|
||||||
{
|
|
||||||
Artist = @"Unknown",
|
|
||||||
Title = @"Sample Beatmap",
|
|
||||||
AuthorString = @"peppy",
|
|
||||||
},
|
|
||||||
Ruleset = new TaikoRuleset().RulesetInfo
|
|
||||||
},
|
|
||||||
ControlPointInfo = controlPointInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
Add(playfieldContainer = new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 768,
|
|
||||||
Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changePlayfieldSize(int step)
|
private void changePlayfieldSize(int step)
|
||||||
@ -128,11 +94,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
|
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
|
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,9 +115,9 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
|
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
|
||||||
|
|
||||||
Add(h);
|
DrawableRuleset.Playfield.Add(h);
|
||||||
|
|
||||||
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStrongHitJudgement(bool kiai)
|
private void addStrongHitJudgement(bool kiai)
|
||||||
@ -166,37 +132,37 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
|
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
|
||||||
|
|
||||||
Add(h);
|
DrawableRuleset.Playfield.Add(h);
|
||||||
|
|
||||||
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
||||||
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMissJudgement()
|
private void addMissJudgement()
|
||||||
{
|
{
|
||||||
DrawableTestHit h;
|
DrawableTestHit h;
|
||||||
Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
|
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
|
||||||
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBarLine(bool major, double delay = scroll_time)
|
private void addBarLine(bool major, double delay = scroll_time)
|
||||||
{
|
{
|
||||||
BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay };
|
BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay };
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
|
DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSwell(double duration = default_duration)
|
private void addSwell(double duration = default_duration)
|
||||||
{
|
{
|
||||||
var swell = new Swell
|
var swell = new Swell
|
||||||
{
|
{
|
||||||
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
Duration = duration,
|
Duration = duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(new DrawableSwell(swell));
|
DrawableRuleset.Playfield.Add(new DrawableSwell(swell));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false)
|
private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false)
|
||||||
@ -206,7 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
var d = new DrumRoll
|
var d = new DrumRoll
|
||||||
{
|
{
|
||||||
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong,
|
IsStrong = strong,
|
||||||
Duration = duration,
|
Duration = duration,
|
||||||
TickRate = 8,
|
TickRate = 8,
|
||||||
@ -217,33 +183,33 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
d.ApplyDefaults(cpi, new BeatmapDifficulty());
|
d.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(new DrawableDrumRoll(d));
|
DrawableRuleset.Playfield.Add(new DrawableDrumRoll(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCentreHit(bool strong)
|
private void addCentreHit(bool strong)
|
||||||
{
|
{
|
||||||
Hit h = new Hit
|
Hit h = new Hit
|
||||||
{
|
{
|
||||||
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong
|
IsStrong = strong
|
||||||
};
|
};
|
||||||
|
|
||||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(new DrawableHit(h));
|
DrawableRuleset.Playfield.Add(new DrawableHit(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRimHit(bool strong)
|
private void addRimHit(bool strong)
|
||||||
{
|
{
|
||||||
Hit h = new Hit
|
Hit h = new Hit
|
||||||
{
|
{
|
||||||
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong
|
IsStrong = strong
|
||||||
};
|
};
|
||||||
|
|
||||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(new DrawableHit(h));
|
DrawableRuleset.Playfield.Add(new DrawableHit(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestStrongNestedHit : DrawableStrongNestedHit
|
private class TestStrongNestedHit : DrawableStrongNestedHit
|
||||||
|
@ -57,7 +57,13 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
ChangeHandler.BeginChange();
|
ChangeHandler.BeginChange();
|
||||||
|
|
||||||
foreach (var h in hits)
|
foreach (var h in hits)
|
||||||
|
{
|
||||||
|
if (h.IsStrong != state)
|
||||||
|
{
|
||||||
h.IsStrong = state;
|
h.IsStrong = state;
|
||||||
|
EditorBeatmap.UpdateHitObject(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChangeHandler.EndChange();
|
ChangeHandler.EndChange();
|
||||||
}
|
}
|
||||||
|
@ -27,5 +27,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadSamples()
|
||||||
|
{
|
||||||
|
// block base call - flying hits are not supposed to play samples
|
||||||
|
// the base call could overwrite the type of this hit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
bool hitButton = true;
|
bool hitButton = true;
|
||||||
|
|
||||||
Frames.Add(new TaikoReplayFrame(-100000));
|
Frames.Add(new TaikoReplayFrame(-100000));
|
||||||
|
@ -15,8 +15,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
internal class DefaultHitExplosion : CircularContainer
|
internal class DefaultHitExplosion : CircularContainer
|
||||||
{
|
{
|
||||||
[Resolved]
|
private readonly DrawableHitObject judgedObject;
|
||||||
private DrawableHitObject judgedObject { get; set; }
|
private readonly HitResult result;
|
||||||
|
|
||||||
|
public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result)
|
||||||
|
{
|
||||||
|
this.judgedObject = judgedObject;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
@ -31,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
Alpha = 0.15f;
|
Alpha = 0.15f;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
if (judgedObject.Result.Type == HitResult.Miss)
|
if (result == HitResult.Miss)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
|
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
|
||||||
|
@ -25,15 +25,18 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
[Cached(typeof(DrawableHitObject))]
|
[Cached(typeof(DrawableHitObject))]
|
||||||
public readonly DrawableHitObject JudgedObject;
|
public readonly DrawableHitObject JudgedObject;
|
||||||
|
|
||||||
|
private readonly HitResult result;
|
||||||
|
|
||||||
private SkinnableDrawable skinnable;
|
private SkinnableDrawable skinnable;
|
||||||
|
|
||||||
public override double LifetimeStart => skinnable.Drawable.LifetimeStart;
|
public override double LifetimeStart => skinnable.Drawable.LifetimeStart;
|
||||||
|
|
||||||
public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd;
|
public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd;
|
||||||
|
|
||||||
public HitExplosion(DrawableHitObject judgedObject)
|
public HitExplosion(DrawableHitObject judgedObject, HitResult result)
|
||||||
{
|
{
|
||||||
JudgedObject = judgedObject;
|
JudgedObject = judgedObject;
|
||||||
|
this.result = result;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -47,14 +50,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion());
|
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject)
|
private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject)
|
||||||
{
|
{
|
||||||
var resultType = judgedObject.Result?.Type ?? HitResult.Great;
|
switch (result)
|
||||||
|
|
||||||
switch (resultType)
|
|
||||||
{
|
{
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
return TaikoSkinComponents.TaikoExplosionMiss;
|
return TaikoSkinComponents.TaikoExplosionMiss;
|
||||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
@ -206,8 +207,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
});
|
});
|
||||||
|
|
||||||
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
|
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
|
||||||
|
addExplosion(judgedObject, result.Type, type);
|
||||||
addExplosion(judgedObject, type);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,9 +219,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
/// As legacy skins have different explosions for singular and double strong hits,
|
/// As legacy skins have different explosions for singular and double strong hits,
|
||||||
/// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame.
|
/// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() =>
|
private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() =>
|
||||||
{
|
{
|
||||||
hitExplosionContainer.Add(new HitExplosion(drawableObject));
|
hitExplosionContainer.Add(new HitExplosion(drawableObject, result));
|
||||||
if (drawableObject.HitObject.Kiai)
|
if (drawableObject.HitObject.Kiai)
|
||||||
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
|
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing
|
namespace osu.Game.Tests.Editing
|
||||||
@ -13,11 +15,12 @@ namespace osu.Game.Tests.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSaveRestoreState()
|
public void TestSaveRestoreState()
|
||||||
{
|
{
|
||||||
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()));
|
var (handler, beatmap) = createChangeHandler();
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
Assert.That(handler.CanRedo.Value, Is.False);
|
Assert.That(handler.CanRedo.Value, Is.False);
|
||||||
|
|
||||||
|
addArbitraryChange(beatmap);
|
||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.True);
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
@ -29,15 +32,48 @@ namespace osu.Game.Tests.Editing
|
|||||||
Assert.That(handler.CanRedo.Value, Is.True);
|
Assert.That(handler.CanRedo.Value, Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveSameStateDoesNotSave()
|
||||||
|
{
|
||||||
|
var (handler, beatmap) = createChangeHandler();
|
||||||
|
|
||||||
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
|
Assert.That(handler.CanRedo.Value, Is.False);
|
||||||
|
|
||||||
|
addArbitraryChange(beatmap);
|
||||||
|
handler.SaveState();
|
||||||
|
|
||||||
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
|
Assert.That(handler.CanRedo.Value, Is.False);
|
||||||
|
|
||||||
|
string hash = handler.CurrentStateHash;
|
||||||
|
|
||||||
|
// save a save without making any changes
|
||||||
|
handler.SaveState();
|
||||||
|
|
||||||
|
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
|
||||||
|
|
||||||
|
handler.RestoreState(-1);
|
||||||
|
|
||||||
|
Assert.That(hash, Is.Not.EqualTo(handler.CurrentStateHash));
|
||||||
|
|
||||||
|
// we should only be able to restore once even though we saved twice.
|
||||||
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
|
Assert.That(handler.CanRedo.Value, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMaxStatesSaved()
|
public void TestMaxStatesSaved()
|
||||||
{
|
{
|
||||||
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()));
|
var (handler, beatmap) = createChangeHandler();
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
|
|
||||||
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
|
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
|
||||||
|
{
|
||||||
|
addArbitraryChange(beatmap);
|
||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
|
}
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.True);
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
|
|
||||||
@ -53,12 +89,15 @@ namespace osu.Game.Tests.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMaxStatesExceeded()
|
public void TestMaxStatesExceeded()
|
||||||
{
|
{
|
||||||
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap()));
|
var (handler, beatmap) = createChangeHandler();
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
|
|
||||||
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++)
|
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++)
|
||||||
|
{
|
||||||
|
addArbitraryChange(beatmap);
|
||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
|
}
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.True);
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
|
|
||||||
@ -70,5 +109,17 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private (EditorChangeHandler, EditorBeatmap) createChangeHandler()
|
||||||
|
{
|
||||||
|
var beatmap = new EditorBeatmap(new Beatmap());
|
||||||
|
|
||||||
|
return (new EditorChangeHandler(beatmap), beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addArbitraryChange(EditorBeatmap beatmap)
|
||||||
|
{
|
||||||
|
beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
@ -175,6 +178,24 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
assertHealthNotEqualTo(0);
|
assertHealthNotEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleLongObjectDoesNotDrain()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = { new JudgeableLongHitObject() }
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.HitObjects[0].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
createProcessor(beatmap);
|
||||||
|
setTime(0);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
|
||||||
|
setTime(5000);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
@ -235,5 +256,23 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class JudgeableLongHitObject : JudgeableHitObject, IHasDuration
|
||||||
|
{
|
||||||
|
public double EndTime => StartTime + Duration;
|
||||||
|
public double Duration { get; set; } = 5000;
|
||||||
|
|
||||||
|
public JudgeableLongHitObject()
|
||||||
|
: base(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
|
AddNested(new JudgeableHitObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
|
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddUntilStep("gameplay has started",
|
||||||
|
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivation()
|
public void TestGameplayOverlayActivation()
|
||||||
{
|
{
|
||||||
@ -21,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivationPaused()
|
public void TestGameplayOverlayActivationPaused()
|
||||||
{
|
{
|
||||||
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
AddStep("pause gameplay", () => Player.Pause());
|
AddStep("pause gameplay", () => Player.Pause());
|
||||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
// needs to be set initially for the ResizeTo to respect minimum size
|
||||||
|
Size = new Vector2(SCROLL_BAR_HEIGHT);
|
||||||
|
|
||||||
const float margin = 3;
|
const float margin = 3;
|
||||||
|
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
|
@ -45,15 +45,21 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
beatmap.HitObjectAdded += addHitObject;
|
beatmap.HitObjectAdded += addHitObject;
|
||||||
|
beatmap.HitObjectUpdated += updateReplay;
|
||||||
beatmap.HitObjectRemoved += removeHitObject;
|
beatmap.HitObjectRemoved += removeHitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateReplay(HitObject obj = null) =>
|
||||||
|
drawableRuleset.RegenerateAutoplay();
|
||||||
|
|
||||||
private void addHitObject(HitObject hitObject)
|
private void addHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject);
|
var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject);
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(drawableObject);
|
drawableRuleset.Playfield.Add(drawableObject);
|
||||||
drawableRuleset.Playfield.PostProcess();
|
drawableRuleset.Playfield.PostProcess();
|
||||||
|
|
||||||
|
updateReplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeHitObject(HitObject hitObject)
|
private void removeHitObject(HitObject hitObject)
|
||||||
@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
drawableRuleset.Playfield.Remove(drawableObject);
|
drawableRuleset.Playfield.Remove(drawableObject);
|
||||||
drawableRuleset.Playfield.PostProcess();
|
drawableRuleset.Playfield.PostProcess();
|
||||||
|
|
||||||
|
drawableRuleset.RegenerateAutoplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool PropagatePositionalInputSubTree => false;
|
public override bool PropagatePositionalInputSubTree => false;
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap))
|
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() }))
|
||||||
{
|
{
|
||||||
Clock = EditorClock,
|
Clock = EditorClock,
|
||||||
ProcessCustomClock = false
|
ProcessCustomClock = false
|
||||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
private double computeDrainRate()
|
private double computeDrainRate()
|
||||||
{
|
{
|
||||||
if (healthIncreases.Count == 0)
|
if (healthIncreases.Count <= 1)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int adjustment = 1;
|
int adjustment = 1;
|
||||||
|
@ -151,8 +151,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer();
|
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config, CancellationToken? cancellationToken)
|
private void load(CancellationToken? cancellationToken)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -178,11 +181,18 @@ namespace osu.Game.Rulesets.UI
|
|||||||
.WithChild(ResumeOverlay)));
|
.WithChild(ResumeOverlay)));
|
||||||
}
|
}
|
||||||
|
|
||||||
applyRulesetMods(Mods, config);
|
RegenerateAutoplay();
|
||||||
|
|
||||||
loadObjects(cancellationToken);
|
loadObjects(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegenerateAutoplay()
|
||||||
|
{
|
||||||
|
// for now this is applying mods which aren't just autoplay.
|
||||||
|
// we'll need to reconsider this flow in the future.
|
||||||
|
applyRulesetMods(Mods, config);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates and adds drawable representations of hit objects to the play field.
|
/// Creates and adds drawable representations of hit objects to the play field.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -126,9 +126,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!FrameStablePlayback)
|
if (FrameStablePlayback)
|
||||||
return;
|
{
|
||||||
|
|
||||||
if (firstConsumption)
|
if (firstConsumption)
|
||||||
{
|
{
|
||||||
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
|
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
|
||||||
@ -148,18 +147,43 @@ namespace osu.Game.Rulesets.UI
|
|||||||
? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
|
? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
|
||||||
: Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
|
: Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isAttached)
|
if (isAttached)
|
||||||
{
|
{
|
||||||
double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime);
|
double? newTime;
|
||||||
|
|
||||||
|
if (FrameStablePlayback)
|
||||||
|
{
|
||||||
|
// when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy.
|
||||||
|
if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null)
|
||||||
|
{
|
||||||
|
// setting invalid state here ensures that gameplay will not continue (ie. our child
|
||||||
|
// hierarchy won't be updated).
|
||||||
|
validState = false;
|
||||||
|
|
||||||
|
// potentially loop to catch-up playback.
|
||||||
|
requireMoreUpdateLoops = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// when stability is disabled, we don't really care about accuracy.
|
||||||
|
// looping over the replay will allow it to catch up and feed out the required values
|
||||||
|
// for the current time.
|
||||||
|
while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime)
|
||||||
|
{
|
||||||
if (newTime == null)
|
if (newTime == null)
|
||||||
{
|
{
|
||||||
// we shouldn't execute for this time value. probably waiting on more replay data.
|
// special case for when the replay actually can't arrive at the required time.
|
||||||
|
// protects from potential endless loop.
|
||||||
validState = false;
|
validState = false;
|
||||||
requireMoreUpdateLoops = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newProposedTime = newTime.Value;
|
newProposedTime = newTime.Value;
|
||||||
}
|
}
|
||||||
|
@ -201,6 +201,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
if (isDraggingBlueprint)
|
if (isDraggingBlueprint)
|
||||||
{
|
{
|
||||||
|
// handle positional change etc.
|
||||||
|
foreach (var obj in selectedHitObjects)
|
||||||
|
Beatmap.UpdateHitObject(obj);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
isDraggingBlueprint = false;
|
isDraggingBlueprint = false;
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
if (blueprint != null)
|
if (blueprint != null)
|
||||||
{
|
{
|
||||||
|
// doing this post-creations as adding the default hit sample should be the case regardless of the ruleset.
|
||||||
|
blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
|
||||||
|
|
||||||
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
||||||
|
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
||||||
|
@ -288,8 +288,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
var comboInfo = h as IHasComboInformation;
|
var comboInfo = h as IHasComboInformation;
|
||||||
|
|
||||||
if (comboInfo == null)
|
if (comboInfo == null || comboInfo.NewCombo == state) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
comboInfo.NewCombo = state;
|
comboInfo.NewCombo = state;
|
||||||
EditorBeatmap?.UpdateHitObject(h);
|
EditorBeatmap?.UpdateHitObject(h);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -89,24 +90,28 @@ namespace osu.Game.Screens.Edit
|
|||||||
if (isRestoring)
|
if (isRestoring)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
|
||||||
|
|
||||||
|
var newState = stream.ToArray();
|
||||||
|
|
||||||
|
// if the previous state is binary equal we don't need to push a new one, unless this is the initial state.
|
||||||
|
if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return;
|
||||||
|
|
||||||
if (currentState < savedStates.Count - 1)
|
if (currentState < savedStates.Count - 1)
|
||||||
savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1);
|
savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1);
|
||||||
|
|
||||||
if (savedStates.Count > MAX_SAVED_STATES)
|
if (savedStates.Count > MAX_SAVED_STATES)
|
||||||
savedStates.RemoveAt(0);
|
savedStates.RemoveAt(0);
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
savedStates.Add(newState);
|
||||||
{
|
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
|
||||||
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
|
|
||||||
|
|
||||||
savedStates.Add(stream.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState = savedStates.Count - 1;
|
currentState = savedStates.Count - 1;
|
||||||
|
|
||||||
updateBindables();
|
updateBindables();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restores an older or newer state.
|
/// Restores an older or newer state.
|
||||||
|
Loading…
Reference in New Issue
Block a user