mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge branch 'master' into always-use-lifetime-entry
This commit is contained in:
commit
ef81bdf63f
@ -1,28 +1,22 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.EmptyFreeform.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
{
|
||||
public class EmptyFreeformAutoGenerator : AutoGenerator
|
||||
public class EmptyFreeformAutoGenerator : AutoGenerator<EmptyFreeformReplayFrame>
|
||||
{
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
public new Beatmap<EmptyFreeformHitObject> Beatmap => (Beatmap<EmptyFreeformHitObject>)base.Beatmap;
|
||||
|
||||
public EmptyFreeformAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
Frames.Add(new EmptyFreeformReplayFrame());
|
||||
|
||||
@ -35,8 +29,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
// todo: add required inputs and extra frames.
|
||||
});
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,22 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Pippidon.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
{
|
||||
public class PippidonAutoGenerator : AutoGenerator
|
||||
public class PippidonAutoGenerator : AutoGenerator<PippidonReplayFrame>
|
||||
{
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
public new Beatmap<PippidonHitObject> Beatmap => (Beatmap<PippidonHitObject>)base.Beatmap;
|
||||
|
||||
public PippidonAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
Frames.Add(new PippidonReplayFrame());
|
||||
|
||||
@ -34,8 +28,6 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
Position = hitObject.Position,
|
||||
});
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,22 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.EmptyScrolling.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
{
|
||||
public class EmptyScrollingAutoGenerator : AutoGenerator
|
||||
public class EmptyScrollingAutoGenerator : AutoGenerator<EmptyScrollingReplayFrame>
|
||||
{
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
public new Beatmap<EmptyScrollingHitObject> Beatmap => (Beatmap<EmptyScrollingHitObject>)base.Beatmap;
|
||||
|
||||
public EmptyScrollingAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
Frames.Add(new EmptyScrollingReplayFrame());
|
||||
|
||||
@ -34,8 +28,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
// todo: add required inputs and extra frames.
|
||||
});
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,29 +2,23 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Pippidon.Objects;
|
||||
using osu.Game.Rulesets.Pippidon.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
{
|
||||
public class PippidonAutoGenerator : AutoGenerator
|
||||
public class PippidonAutoGenerator : AutoGenerator<PippidonReplayFrame>
|
||||
{
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
public new Beatmap<PippidonHitObject> Beatmap => (Beatmap<PippidonHitObject>)base.Beatmap;
|
||||
|
||||
public PippidonAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
int currentLane = 0;
|
||||
|
||||
@ -55,8 +49,6 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
|
||||
currentLane = hitObject.Lane;
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
|
||||
private void addFrame(double time, PippidonAction direction)
|
||||
|
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.427.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.513.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -28,10 +28,12 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false;
|
||||
}
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
base.ApplyNormalVisibilityState(hitObject, state);
|
||||
|
||||
if (!(hitObject is DrawableCatchHitObject catchDrawable))
|
||||
return;
|
||||
|
||||
@ -54,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
var offset = hitObject.TimePreempt * fade_out_offset_multiplier;
|
||||
var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
|
||||
|
||||
using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true))
|
||||
using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset))
|
||||
drawable.FadeOut(duration);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
@ -13,26 +12,19 @@ using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Replays
|
||||
{
|
||||
internal class CatchAutoGenerator : AutoGenerator
|
||||
internal class CatchAutoGenerator : AutoGenerator<CatchReplayFrame>
|
||||
{
|
||||
public const double RELEASE_DELAY = 20;
|
||||
|
||||
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
||||
|
||||
public CatchAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
protected Replay Replay;
|
||||
|
||||
private CatchReplayFrame currentFrame;
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
if (Beatmap.HitObjects.Count == 0)
|
||||
return Replay;
|
||||
return;
|
||||
|
||||
// todo: add support for HT DT
|
||||
const double dash_speed = Catcher.BASE_SPEED;
|
||||
@ -119,15 +111,11 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
|
||||
private void addFrame(double time, float? position = null, bool dashing = false)
|
||||
{
|
||||
var last = currentFrame;
|
||||
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
||||
Replay.Frames.Add(currentFrame);
|
||||
Frames.Add(new CatchReplayFrame(time, position, dashing, LastFrame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
explosion = new LegacyRollingCounter(skin, LegacyFont.Combo)
|
||||
explosion = new LegacyRollingCounter(LegacyFont.Combo)
|
||||
{
|
||||
Alpha = 0.65f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.5f),
|
||||
},
|
||||
counter = new LegacyRollingCounter(skin, LegacyFont.Combo)
|
||||
counter = new LegacyRollingCounter(LegacyFont.Combo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
}
|
||||
};
|
||||
|
||||
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
|
||||
AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
}
|
||||
|
||||
private void setScrollStep(ScrollingDirection direction)
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
Child = drawableObject = new DrawableNote(note)
|
||||
};
|
||||
|
||||
AddBlueprint(new NoteSelectionBlueprint(drawableObject));
|
||||
AddBlueprint(new NoteSelectionBlueprint(note), drawableObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneTimingBasedNoteColouring : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private RulesetConfigCache configCache { get; set; }
|
||||
|
||||
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
const double beat_length = 500;
|
||||
|
||||
var ruleset = new ManiaRuleset();
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 0 },
|
||||
new Note { StartTime = beat_length / 16 },
|
||||
new Note { StartTime = beat_length / 12 },
|
||||
new Note { StartTime = beat_length / 8 },
|
||||
new Note { StartTime = beat_length / 6 },
|
||||
new Note { StartTime = beat_length / 4 },
|
||||
new Note { StartTime = beat_length / 3 },
|
||||
new Note { StartTime = beat_length / 2 },
|
||||
new Note { StartTime = beat_length }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo(),
|
||||
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
|
||||
};
|
||||
|
||||
foreach (var note in beatmap.HitObjects)
|
||||
{
|
||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
}
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
||||
{
|
||||
BeatLength = beat_length
|
||||
});
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
ruleset.CreateDrawableRulesetWith(beatmap)
|
||||
}
|
||||
};
|
||||
|
||||
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||
|
||||
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
|
||||
SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
|
||||
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
|
||||
}
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||
@ -34,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
public enum ManiaRulesetSetting
|
||||
{
|
||||
ScrollTime,
|
||||
ScrollDirection
|
||||
ScrollDirection,
|
||||
TimingBasedNoteColouring
|
||||
}
|
||||
}
|
||||
|
@ -2,34 +2,35 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint
|
||||
public class HoldNoteNoteOverlay : CompositeDrawable
|
||||
{
|
||||
protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
||||
|
||||
private readonly HoldNoteSelectionBlueprint holdNoteBlueprint;
|
||||
private readonly HoldNotePosition position;
|
||||
|
||||
public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position)
|
||||
: base(holdNote)
|
||||
public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position)
|
||||
{
|
||||
this.holdNoteBlueprint = holdNoteBlueprint;
|
||||
this.position = position;
|
||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
||||
|
||||
Select();
|
||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var drawableObject = holdNoteBlueprint.DrawableObject;
|
||||
|
||||
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
||||
if (DrawableObject.IsLoaded)
|
||||
if (drawableObject.IsLoaded)
|
||||
{
|
||||
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
|
||||
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail;
|
||||
|
||||
Anchor = note.Anchor;
|
||||
Origin = note.Origin;
|
||||
@ -38,8 +39,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
Position = note.DrawPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
|
||||
public override bool HandlePositionalInput => false;
|
||||
}
|
||||
}
|
@ -8,13 +8,14 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
|
||||
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||
{
|
||||
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
||||
|
||||
@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
|
||||
public HoldNoteSelectionBlueprint(HoldNote hold)
|
||||
: base(hold)
|
||||
{
|
||||
}
|
||||
@ -32,16 +33,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
|
||||
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
|
||||
new HoldNoteNoteOverlay(this, HoldNotePosition.Start),
|
||||
new HoldNoteNoteOverlay(this, HoldNotePosition.End),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -4,22 +4,23 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
|
||||
public abstract class ManiaSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
||||
where T : ManiaHitObject
|
||||
{
|
||||
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
||||
: base(drawableObject)
|
||||
protected ManiaSelectionBlueprint(T hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
}
|
||||
|
@ -3,13 +3,13 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public class NoteSelectionBlueprint : ManiaSelectionBlueprint
|
||||
public class NoteSelectionBlueprint : ManiaSelectionBlueprint<Note>
|
||||
{
|
||||
public NoteSelectionBlueprint(DrawableNote note)
|
||||
public NoteSelectionBlueprint(Note note)
|
||||
: base(note)
|
||||
{
|
||||
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
@ -16,20 +16,20 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
|
||||
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableNote note:
|
||||
case Note note:
|
||||
return new NoteSelectionBlueprint(note);
|
||||
|
||||
case DrawableHoldNote holdNote:
|
||||
case HoldNote holdNote:
|
||||
return new HoldNoteSelectionBlueprint(holdNote);
|
||||
}
|
||||
|
||||
return base.CreateBlueprintFor(hitObject);
|
||||
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||
}
|
||||
|
||||
protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler();
|
||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public class ManiaSelectionHandler : SelectionHandler
|
||||
public class ManiaSelectionHandler : EditorSelectionHandler
|
||||
{
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
@ -20,21 +20,21 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
[Resolved]
|
||||
private HitObjectComposer composer { get; set; }
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
|
||||
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
|
||||
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
||||
int lastColumn = ((ManiaHitObject)hitObjectBlueprint.Item).Column;
|
||||
|
||||
performColumnMovement(lastColumn, moveEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||
|
||||
var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||
var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.Blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta);
|
||||
if (currentColumn == null)
|
||||
return;
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
||||
|
||||
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.2);
|
||||
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
||||
|
||||
|
@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||
KeyboardStep = 5
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Timing-based note colouring",
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
@ -39,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
@ -24,6 +25,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
[Resolved(canBeNull: true)]
|
||||
private ManiaPlayfield playfield { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the samples that are played by this object during gameplay.
|
||||
/// </summary>
|
||||
public ISampleInfo[] GetGameplaySamples() => Samples.Samples;
|
||||
|
||||
protected override float SamplePlaybackPosition
|
||||
{
|
||||
get
|
||||
|
@ -2,13 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -17,6 +23,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IBeatmap beatmap { get; set; }
|
||||
|
||||
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
|
||||
|
||||
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
||||
|
||||
private readonly Drawable headPiece;
|
||||
@ -34,6 +48,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(ManiaRulesetConfigManager rulesetConfig)
|
||||
{
|
||||
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour());
|
||||
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true);
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(e);
|
||||
@ -73,5 +99,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
public virtual void OnReleased(ManiaAction action)
|
||||
{
|
||||
}
|
||||
|
||||
private void updateSnapColour()
|
||||
{
|
||||
if (beatmap == null) return;
|
||||
|
||||
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
||||
|
||||
Colour = configTimingBasedNoteColouring.Value ? BindableBeatDivisor.GetColourFor(snapDivisor, colours) : Color4.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Mania.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -11,7 +10,7 @@ using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Replays
|
||||
{
|
||||
internal class ManiaAutoGenerator : AutoGenerator
|
||||
internal class ManiaAutoGenerator : AutoGenerator<ManiaReplayFrame>
|
||||
{
|
||||
public const double RELEASE_DELAY = 20;
|
||||
|
||||
@ -22,8 +21,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
|
||||
columnActions = new ManiaAction[Beatmap.TotalColumns];
|
||||
|
||||
var normalAction = ManiaAction.Key1;
|
||||
@ -43,12 +40,10 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
}
|
||||
}
|
||||
|
||||
protected Replay Replay;
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
if (Beatmap.HitObjects.Count == 0)
|
||||
return Replay;
|
||||
return;
|
||||
|
||||
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
||||
|
||||
@ -70,10 +65,8 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
}
|
||||
}
|
||||
|
||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
||||
Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
|
||||
private IEnumerable<IActionPoint> generateActionPoints()
|
||||
|
@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
public const float COLUMN_WIDTH = 80;
|
||||
public const float SPECIAL_COLUMN_WIDTH = 70;
|
||||
|
||||
/// <summary>
|
||||
/// For hitsounds played by this <see cref="Column"/> (i.e. not as a result of hitting a hitobject),
|
||||
/// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key.
|
||||
/// </summary>
|
||||
private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||
|
||||
/// <summary>
|
||||
/// The index of this column as part of the whole playfield.
|
||||
/// </summary>
|
||||
@ -38,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
internal readonly Container TopLevelContainer;
|
||||
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
||||
private readonly OrderedHitPolicy hitPolicy;
|
||||
private readonly Container<SkinnableSound> hitSounds;
|
||||
|
||||
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||
|
||||
@ -64,6 +71,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
background,
|
||||
hitSounds = new Container<SkinnableSound>
|
||||
{
|
||||
Name = "Column samples pool",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray()
|
||||
},
|
||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
@ -120,6 +133,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||
}
|
||||
|
||||
private int nextHitSoundIndex;
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
{
|
||||
if (action != Action.Value)
|
||||
@ -131,7 +146,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
|
||||
HitObjectContainer.Objects.LastOrDefault();
|
||||
|
||||
nextObject?.PlaySamples();
|
||||
if (nextObject is DrawableManiaHitObject maniaObject)
|
||||
{
|
||||
var hitSound = hitSounds[nextHitSoundIndex];
|
||||
|
||||
hitSound.Samples = maniaObject.GetGameplaySamples();
|
||||
hitSound.Play();
|
||||
|
||||
nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||
@ -224,12 +225,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||
|
||||
private void assertOk(IBeatmap beatmap)
|
||||
{
|
||||
Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty);
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertOffscreenCircle(IBeatmap beatmap)
|
||||
{
|
||||
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle);
|
||||
@ -237,7 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||
|
||||
private void assertOffscreenSlider(IBeatmap beatmap)
|
||||
{
|
||||
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider);
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||
|
||||
Add(drawableObject = new DrawableHitCircle(hitCircle));
|
||||
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
|
||||
AddBlueprint(blueprint = new TestBlueprint(hitCircle), drawableObject);
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||
|
||||
public TestBlueprint(DrawableHitCircle drawableCircle)
|
||||
: base(drawableCircle)
|
||||
public TestBlueprint(HitCircle circle)
|
||||
: base(circle)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||
|
||||
Add(drawableObject = new DrawableSlider(slider));
|
||||
AddBlueprint(new TestSliderBlueprint(drawableObject));
|
||||
AddBlueprint(new TestSliderBlueprint(slider), drawableObject);
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -150,23 +150,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||
{
|
||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
|
||||
public TestSliderBlueprint(DrawableSlider slider)
|
||||
public TestSliderBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
||||
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
|
||||
}
|
||||
|
||||
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
||||
private class TestSliderCircleOverlay : SliderCircleOverlay
|
||||
{
|
||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||
|
||||
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
||||
public TestSliderCircleOverlay(Slider slider, SliderPosition position)
|
||||
: base(slider, position)
|
||||
{
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||
|
||||
Add(drawableObject = new DrawableSlider(slider));
|
||||
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
|
||||
AddBlueprint(blueprint = new TestSliderBlueprint(slider), drawableObject);
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -174,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
|
||||
|
||||
AddAssert("head positioned correctly",
|
||||
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
|
||||
() => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
|
||||
|
||||
AddAssert("tail positioned correctly",
|
||||
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
||||
() => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
||||
}
|
||||
|
||||
private void moveMouseToControlPoint(int index)
|
||||
@ -195,23 +195,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||
{
|
||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
|
||||
public TestSliderBlueprint(DrawableSlider slider)
|
||||
public TestSliderBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
||||
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
|
||||
}
|
||||
|
||||
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
||||
private class TestSliderCircleOverlay : SliderCircleOverlay
|
||||
{
|
||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||
|
||||
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
||||
public TestSliderCircleOverlay(Slider slider, SliderPosition position)
|
||||
: base(slider, position)
|
||||
{
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
Child = drawableSpinner = new DrawableSpinner(spinner)
|
||||
});
|
||||
|
||||
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
|
||||
AddBlueprint(new SpinnerSelectionBlueprint(spinner) { Size = new Vector2(0.5f) }, drawableSpinner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
Child = new SkinProvidingContainer(new DefaultSkin())
|
||||
Child = new SkinProvidingContainer(new DefaultSkin(null))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
||||
|
@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||
|
||||
protected readonly HitCirclePiece CirclePiece;
|
||||
|
||||
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
|
||||
: base(drawableCircle)
|
||||
public HitCircleSelectionBlueprint(HitCircle circle)
|
||||
: base(circle)
|
||||
{
|
||||
InternalChild = CirclePiece = new HitCirclePiece();
|
||||
}
|
||||
|
@ -2,20 +2,20 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
public abstract class OsuSelectionBlueprint<T> : OverlaySelectionBlueprint
|
||||
public abstract class OsuSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
||||
where T : OsuHitObject
|
||||
{
|
||||
protected new T HitObject => (T)DrawableObject.HitObject;
|
||||
protected new DrawableOsuHitObject DrawableObject => (DrawableOsuHitObject)base.DrawableObject;
|
||||
|
||||
protected override bool AlwaysShowWhenSelected => true;
|
||||
|
||||
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
|
||||
: base(drawableObject)
|
||||
protected OsuSelectionBlueprint(T hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
AccentColour = Color4.Transparent
|
||||
};
|
||||
|
||||
// SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur.
|
||||
// Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling.
|
||||
AlwaysPresent = true;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -1,36 +1,32 @@
|
||||
// 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.
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||
public class SliderCircleOverlay : CompositeDrawable
|
||||
{
|
||||
protected readonly HitCirclePiece CirclePiece;
|
||||
|
||||
private readonly Slider slider;
|
||||
private readonly SliderPosition position;
|
||||
|
||||
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
|
||||
: base(slider)
|
||||
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
||||
{
|
||||
this.slider = slider;
|
||||
this.position = position;
|
||||
|
||||
InternalChild = CirclePiece = new HitCirclePiece();
|
||||
|
||||
Select();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle);
|
||||
CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle);
|
||||
}
|
||||
|
||||
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
|
||||
public override bool HandlePositionalInput => false;
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
using osuTK;
|
||||
@ -27,14 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||
{
|
||||
protected SliderBodyPiece BodyPiece { get; private set; }
|
||||
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; }
|
||||
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; }
|
||||
protected SliderCircleOverlay HeadOverlay { get; private set; }
|
||||
protected SliderCircleOverlay TailOverlay { get; private set; }
|
||||
|
||||
[CanBeNull]
|
||||
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
|
||||
|
||||
private readonly DrawableSlider slider;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private HitObjectComposer composer { get; set; }
|
||||
|
||||
@ -52,10 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||
public SliderSelectionBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
{
|
||||
this.slider = slider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -64,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
BodyPiece = new SliderBodyPiece(),
|
||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
|
||||
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
||||
};
|
||||
}
|
||||
|
||||
@ -103,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
protected override void OnSelected()
|
||||
{
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true)
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||
{
|
||||
RemoveControlPointsRequested = removeControlPoints
|
||||
});
|
||||
@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||
if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength)
|
||||
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
||||
{
|
||||
placementHandler?.Delete(HitObject);
|
||||
return;
|
||||
@ -245,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
|
||||
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
|
||||
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
{
|
||||
private readonly SpinnerPiece piece;
|
||||
|
||||
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
|
||||
public SpinnerSelectionBlueprint(Spinner spinner)
|
||||
: base(spinner)
|
||||
{
|
||||
InternalChild = piece = new SpinnerPiece();
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||
new IssueTemplateOffscreenSlider(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
foreach (var hitobject in playableBeatmap.HitObjects)
|
||||
foreach (var hitobject in context.Beatmap.HitObjects)
|
||||
{
|
||||
switch (hitobject)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||
@ -17,9 +16,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new CheckOffscreenObjects()
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap)
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap));
|
||||
return checks.SelectMany(check => check.Run(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
@ -18,23 +18,23 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
}
|
||||
|
||||
protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
|
||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
|
||||
|
||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
|
||||
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
case HitCircle circle:
|
||||
return new HitCircleSelectionBlueprint(circle);
|
||||
|
||||
case DrawableSlider slider:
|
||||
case Slider slider:
|
||||
return new SliderSelectionBlueprint(slider);
|
||||
|
||||
case DrawableSpinner spinner:
|
||||
case Spinner spinner:
|
||||
return new SpinnerSelectionBlueprint(spinner);
|
||||
}
|
||||
|
||||
return base.CreateBlueprintFor(hitObject);
|
||||
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
if (b.IsSelected)
|
||||
continue;
|
||||
|
||||
var hitObject = (OsuHitObject)b.HitObject;
|
||||
var hitObject = (OsuHitObject)b.Item;
|
||||
|
||||
Vector2? snap = checkSnap(hitObject.Position);
|
||||
if (snap == null && hitObject.Position != hitObject.EndPosition)
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -15,7 +16,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class OsuSelectionHandler : SelectionHandler
|
||||
public class OsuSelectionHandler : EditorSelectionHandler
|
||||
{
|
||||
protected override void OnSelectionChanged()
|
||||
{
|
||||
@ -36,13 +37,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
referencePathTypes = null;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
|
||||
// this will potentially move the selection out of bounds...
|
||||
foreach (var h in hitObjects)
|
||||
h.Position += moveEvent.InstantDelta;
|
||||
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
|
||||
// but this will be corrected.
|
||||
moveSelectionInBounds();
|
||||
@ -374,8 +375,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// <summary>
|
||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||
/// </summary>
|
||||
private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects
|
||||
.OfType<OsuHitObject>()
|
||||
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||
.Where(h => !(h is Spinner))
|
||||
.ToArray();
|
||||
|
||||
|
@ -2,53 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToDrawableHitObjects
|
||||
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObjects
|
||||
{
|
||||
private float currentRotation;
|
||||
|
||||
[SettingSource("Roll speed", "Rotations per minute")]
|
||||
public BindableNumber<double> SpinSpeed { get; } = new BindableDouble(0.5)
|
||||
{
|
||||
MinValue = 0.02,
|
||||
MaxValue = 12,
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
[SettingSource("Direction", "The direction of rotation")]
|
||||
public Bindable<RotationDirection> Direction { get; } = new Bindable<RotationDirection>(RotationDirection.Clockwise);
|
||||
|
||||
public override string Name => "Barrel Roll";
|
||||
public override string Acronym => "BR";
|
||||
public override string Description => "The whole playfield is on a wheel!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}";
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
playfield.Rotation = currentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// scale the playfield to allow all hitobjects to stay within the visible region.
|
||||
drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
foreach (var d in drawables)
|
||||
@ -58,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
switch (d)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
circle.CirclePiece.Rotation = -currentRotation;
|
||||
circle.CirclePiece.Rotation = -CurrentRotation;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@ -43,13 +43,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
base.ApplyIncreasedVisibilityState(hitObject, state);
|
||||
applyState(hitObject, true);
|
||||
}
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
base.ApplyNormalVisibilityState(hitObject, state);
|
||||
applyState(hitObject, false);
|
||||
}
|
||||
|
||||
@ -60,20 +58,20 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
OsuHitObject hitObject = drawableOsuObject.HitObject;
|
||||
|
||||
(double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject);
|
||||
(double fadeStartTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject);
|
||||
|
||||
switch (drawableObject)
|
||||
{
|
||||
case DrawableSliderTail _:
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
drawableObject.FadeOut(fadeOut.duration);
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
|
||||
drawableObject.FadeOut(fadeDuration);
|
||||
|
||||
break;
|
||||
|
||||
case DrawableSliderRepeat sliderRepeat:
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
|
||||
// only apply to circle piece – reverse arrow is not affected by hidden.
|
||||
sliderRepeat.CirclePiece.FadeOut(fadeOut.duration);
|
||||
sliderRepeat.CirclePiece.FadeOut(fadeDuration);
|
||||
|
||||
break;
|
||||
|
||||
@ -88,23 +86,23 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
else
|
||||
{
|
||||
// we don't want to see the approach circle
|
||||
using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true))
|
||||
using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt))
|
||||
circle.ApproachCircle.Hide();
|
||||
}
|
||||
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
fadeTarget.FadeOut(fadeOut.duration);
|
||||
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
|
||||
fadeTarget.FadeOut(fadeDuration);
|
||||
break;
|
||||
|
||||
case DrawableSlider slider:
|
||||
using (slider.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
slider.Body.FadeOut(fadeOut.duration, Easing.Out);
|
||||
using (slider.BeginAbsoluteSequence(fadeStartTime))
|
||||
slider.Body.FadeOut(fadeDuration, Easing.Out);
|
||||
|
||||
break;
|
||||
|
||||
case DrawableSliderTick sliderTick:
|
||||
using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
sliderTick.FadeOut(fadeOut.duration);
|
||||
using (sliderTick.BeginAbsoluteSequence(fadeStartTime))
|
||||
sliderTick.FadeOut(fadeDuration);
|
||||
|
||||
break;
|
||||
|
||||
@ -112,14 +110,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
// hide elements we don't care about.
|
||||
// todo: hide background
|
||||
|
||||
using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||
spinner.FadeOut(fadeOut.duration);
|
||||
using (spinner.BeginAbsoluteSequence(fadeStartTime))
|
||||
spinner.FadeOut(fadeDuration);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
|
||||
private (double fadeStartTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
|
||||
{
|
||||
switch (drawableObject)
|
||||
{
|
||||
@ -137,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
return getParameters(drawableObject.HitObject);
|
||||
}
|
||||
|
||||
static (double startTime, double duration) getParameters(OsuHitObject hitObject)
|
||||
static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject)
|
||||
{
|
||||
var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn;
|
||||
var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier;
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
Entry = null;
|
||||
}
|
||||
|
||||
private void onEntryInvalidated() => refreshPoints();
|
||||
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
||||
|
||||
private void refreshPoints()
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 115,
|
||||
},
|
||||
bonusCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Position = new Vector2(-87, 445 + spm_hide_offset),
|
||||
},
|
||||
spmCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||
spmCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopRight,
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(Source, LegacyFont.HitCircle)
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
|
@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
|
||||
var point = new HitPoint(pointType, this)
|
||||
{
|
||||
Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255)
|
||||
BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255)
|
||||
};
|
||||
|
||||
points[r][c] = point;
|
||||
@ -234,6 +234,11 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
|
||||
private class HitPoint : Circle
|
||||
{
|
||||
/// <summary>
|
||||
/// The base colour which will be lightened/darkened depending on the value of this <see cref="HitPoint"/>.
|
||||
/// </summary>
|
||||
public Color4 BaseColour;
|
||||
|
||||
private readonly HitPointType pointType;
|
||||
private readonly AccuracyHeatmap heatmap;
|
||||
|
||||
@ -284,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
|
||||
Alpha = Math.Min(amount / lighten_cutoff, 1);
|
||||
if (pointType == HitPointType.Hit)
|
||||
Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff));
|
||||
Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded));
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
|
||||
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
|
||||
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void onJudgmentLoaded(DrawableOsuJudgement judgement)
|
||||
private void onJudgementLoaded(DrawableOsuJudgement judgement)
|
||||
{
|
||||
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
|
||||
using osu.Game.Skinning;
|
||||
@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
}));
|
||||
|
||||
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
|
||||
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss }));
|
||||
new JudgementResult(new HitObject(), new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss }));
|
||||
|
||||
AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
|
||||
}
|
||||
|
@ -3,14 +3,14 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
{
|
||||
public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
|
||||
public class TaikoSelectionBlueprint : HitObjectSelectionBlueprint
|
||||
{
|
||||
public TaikoSelectionBlueprint(DrawableHitObject hitObject)
|
||||
public TaikoSelectionBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
}
|
||||
|
||||
protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler();
|
||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
|
||||
|
||||
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
|
||||
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
||||
new TaikoSelectionBlueprint(hitObject);
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
public class TaikoSelectionHandler : SelectionHandler
|
||||
public class TaikoSelectionHandler : EditorSelectionHandler
|
||||
{
|
||||
private readonly Bindable<TernaryState> selectionRimState = new Bindable<TernaryState>();
|
||||
private readonly Bindable<TernaryState> selectionStrongState = new Bindable<TernaryState>();
|
||||
@ -72,16 +73,19 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
});
|
||||
}
|
||||
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<HitObject>> selection)
|
||||
{
|
||||
if (selection.All(s => s.HitObject is Hit))
|
||||
if (selection.All(s => s.Item is Hit))
|
||||
yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } };
|
||||
|
||||
if (selection.All(s => s.HitObject is TaikoHitObject))
|
||||
if (selection.All(s => s.Item is TaikoHitObject))
|
||||
yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
|
||||
|
||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||
yield return item;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent moveEvent) => true;
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent) => true;
|
||||
|
||||
protected override void UpdateTernaryStates()
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
@ -10,5 +11,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override string Description => @"Beats fade out before you hit them!";
|
||||
public override double ScoreMultiplier => 1.06;
|
||||
public override bool HasImplementation => false;
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public class TaikoModRandom : ModRandom, IApplicableToBeatmap
|
||||
{
|
||||
public override string Description => @"Shuffle around the colours!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModSwap)).ToArray();
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
33
osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs
Normal file
33
osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModSwap : Mod, IApplicableToBeatmap
|
||||
{
|
||||
public override string Name => "Swap";
|
||||
public override string Acronym => "SW";
|
||||
public override string Description => @"Dons become kats, kats become dons";
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var taikoBeatmap = (TaikoBeatmap)beatmap;
|
||||
|
||||
foreach (var obj in taikoBeatmap.HitObjects)
|
||||
{
|
||||
if (obj is Hit hit)
|
||||
hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
@ -13,7 +11,7 @@ using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Replays
|
||||
{
|
||||
public class TaikoAutoGenerator : AutoGenerator
|
||||
public class TaikoAutoGenerator : AutoGenerator<TaikoReplayFrame>
|
||||
{
|
||||
public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap;
|
||||
|
||||
@ -22,16 +20,12 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
public TaikoAutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
}
|
||||
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
|
||||
public override Replay Generate()
|
||||
protected override void GenerateFrames()
|
||||
{
|
||||
if (Beatmap.HitObjects.Count == 0)
|
||||
return Replay;
|
||||
return;
|
||||
|
||||
bool hitButton = true;
|
||||
|
||||
@ -128,8 +122,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
hitButton = !hitButton;
|
||||
}
|
||||
|
||||
return Replay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
base.Update();
|
||||
|
||||
// store X before checking wide enough so if we perform layout there is no positional discrepancy.
|
||||
float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
|
||||
float currentX = (InternalChildren.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
|
||||
|
||||
// ensure we have enough sprites
|
||||
if (!InternalChildren.Any()
|
||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
new TaikoModRandom(),
|
||||
new TaikoModDifficultyAdjust(),
|
||||
new TaikoModClassic(),
|
||||
new TaikoModSwap(),
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[TestFixture]
|
||||
public class BeatmapDifficultyManagerTest
|
||||
public class BeatmapDifficultyCacheTest
|
||||
{
|
||||
[Test]
|
||||
public void TestKeyEqualsWithDifferentModInstances()
|
@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
||||
using osu.Game.IPC;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
@ -264,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
// change filename
|
||||
var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
|
||||
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}"));
|
||||
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}"));
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
@ -40,23 +41,23 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
|
||||
mock.SetupGet(w => w.Track).Returns((Track)null);
|
||||
|
||||
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
|
||||
Assert.That(check.Run(new BeatmapVerifierContext(beatmap, mock.Object)), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAcceptable()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(192);
|
||||
var context = getContext(192);
|
||||
|
||||
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullBitrate()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(null);
|
||||
var context = getContext(null);
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
|
||||
@ -65,9 +66,9 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestZeroBitrate()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(0);
|
||||
var context = getContext(0);
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
|
||||
@ -76,9 +77,9 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestTooHighBitrate()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(320);
|
||||
var context = getContext(320);
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
|
||||
@ -87,14 +88,19 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestTooLowBitrate()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(64);
|
||||
var context = getContext(64);
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate);
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(int? audioBitrate)
|
||||
{
|
||||
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(audioBitrate).Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mock of the working beatmap with the given audio properties.
|
||||
/// </summary>
|
||||
|
@ -9,6 +9,7 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using FileInfo = osu.Game.IO.FileInfo;
|
||||
@ -53,25 +54,25 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
// While this is a problem, it is out of scope for this check and is caught by a different one.
|
||||
beatmap.Metadata.BackgroundFile = null;
|
||||
var mock = getMockWorkingBeatmap(null, System.Array.Empty<byte>());
|
||||
var context = getContext(null, System.Array.Empty<byte>());
|
||||
|
||||
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAcceptable()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(new Texture(1920, 1080));
|
||||
var context = getContext(new Texture(1920, 1080));
|
||||
|
||||
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTooHighResolution()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(new Texture(3840, 2160));
|
||||
var context = getContext(new Texture(3840, 2160));
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution);
|
||||
@ -80,9 +81,9 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestLowResolution()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(new Texture(640, 480));
|
||||
var context = getContext(new Texture(640, 480));
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution);
|
||||
@ -91,9 +92,9 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestTooLowResolution()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(new Texture(100, 100));
|
||||
var context = getContext(new Texture(100, 100));
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution);
|
||||
@ -102,14 +103,19 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestTooUncompressed()
|
||||
{
|
||||
var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]);
|
||||
var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]);
|
||||
|
||||
var issues = check.Run(beatmap, mock.Object).ToList();
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed);
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null)
|
||||
{
|
||||
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mock of the working beatmap with the given background and filesize.
|
||||
/// </summary>
|
||||
|
194
osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs
Normal file
194
osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs
Normal file
@ -0,0 +1,194 @@
|
||||
// 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 System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckConcurrentObjectsTest
|
||||
{
|
||||
private CheckConcurrentObjects check;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckConcurrentObjects();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCirclesSeparate()
|
||||
{
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 150 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCirclesConcurrent()
|
||||
{
|
||||
assertConcurrentSame(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 100 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCirclesAlmostConcurrent()
|
||||
{
|
||||
assertConcurrentSame(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 101 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSlidersSeparate()
|
||||
{
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object,
|
||||
getSliderMock(startTime: 500, endTime: 900.75d).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSlidersConcurrent()
|
||||
{
|
||||
assertConcurrentSame(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object,
|
||||
getSliderMock(startTime: 300, endTime: 700.75d).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSlidersAlmostConcurrent()
|
||||
{
|
||||
assertConcurrentSame(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object,
|
||||
getSliderMock(startTime: 402, endTime: 902.75d).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderAndCircleConcurrent()
|
||||
{
|
||||
assertConcurrentDifferent(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object,
|
||||
new HitCircle { StartTime = 300 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyObjectsConcurrent()
|
||||
{
|
||||
var hitobjects = new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object,
|
||||
getSliderMock(startTime: 200, endTime: 500.75d).Object,
|
||||
new HitCircle { StartTime = 300 }
|
||||
};
|
||||
|
||||
var issues = check.Run(getContext(hitobjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(3));
|
||||
Assert.That(issues.Where(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent).ToList(), Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNotesSeparateOnSameColumn()
|
||||
{
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
|
||||
getHoldNoteMock(startTime: 500, endTime: 900.75d, column: 1).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNotesConcurrentOnDifferentColumns()
|
||||
{
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
|
||||
getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 2).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNotesConcurrentOnSameColumn()
|
||||
{
|
||||
assertConcurrentSame(new List<HitObject>
|
||||
{
|
||||
getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object,
|
||||
getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 1).Object
|
||||
});
|
||||
}
|
||||
|
||||
private Mock<Slider> getSliderMock(double startTime, double endTime, int repeats = 0)
|
||||
{
|
||||
var mock = new Mock<Slider>();
|
||||
mock.SetupGet(s => s.StartTime).Returns(startTime);
|
||||
mock.As<IHasRepeats>().Setup(r => r.RepeatCount).Returns(repeats);
|
||||
mock.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private Mock<HoldNote> getHoldNoteMock(double startTime, double endTime, int column)
|
||||
{
|
||||
var mock = new Mock<HoldNote>();
|
||||
mock.SetupGet(s => s.StartTime).Returns(startTime);
|
||||
mock.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
|
||||
mock.As<IHasColumn>().Setup(c => c.Column).Returns(column);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private void assertOk(List<HitObject> hitobjects)
|
||||
{
|
||||
Assert.That(check.Run(getContext(hitobjects)), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertConcurrentSame(List<HitObject> hitobjects, int count = 1)
|
||||
{
|
||||
var issues = check.Run(getContext(hitobjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(count));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame));
|
||||
}
|
||||
|
||||
private void assertConcurrentDifferent(List<HitObject> hitobjects, int count = 1)
|
||||
{
|
||||
var issues = check.Run(getContext(hitobjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(count));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent));
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(List<HitObject> hitobjects)
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = hitobjects };
|
||||
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@ -45,7 +46,8 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestBackgroundSetAndInFiles()
|
||||
{
|
||||
Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty);
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -53,7 +55,8 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||
|
||||
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist);
|
||||
@ -64,7 +67,8 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
beatmap.Metadata.BackgroundFile = null;
|
||||
|
||||
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet);
|
||||
|
159
osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs
Normal file
159
osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs
Normal file
@ -0,0 +1,159 @@
|
||||
// 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 System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckUnsnappedObjectsTest
|
||||
{
|
||||
private CheckUnsnappedObjects check;
|
||||
private ControlPointInfo cpi;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckUnsnappedObjects();
|
||||
|
||||
cpi = new ControlPointInfo();
|
||||
cpi.Add(100, new TimingControlPoint { BeatLength = 100 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleSnapped()
|
||||
{
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 100 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleUnsnapped1Ms()
|
||||
{
|
||||
assert1Ms(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 101 }
|
||||
});
|
||||
|
||||
assert1Ms(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 99 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleUnsnapped2Ms()
|
||||
{
|
||||
assert2Ms(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 102 }
|
||||
});
|
||||
|
||||
assert2Ms(new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 98 }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderSnapped()
|
||||
{
|
||||
// Slider ends are naturally < 1 ms unsnapped because of how SV works.
|
||||
assertOk(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 100, endTime: 400.75d).Object
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderUnsnapped1Ms()
|
||||
{
|
||||
assert1Ms(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 101, endTime: 401.75d).Object
|
||||
}, count: 2);
|
||||
|
||||
// End is only off by 0.25 ms, hence count 1.
|
||||
assert1Ms(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 99, endTime: 399.75d).Object
|
||||
}, count: 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderUnsnapped2Ms()
|
||||
{
|
||||
assert2Ms(new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 102, endTime: 402.75d).Object
|
||||
}, count: 2);
|
||||
|
||||
// Start and end are 2 ms and 1.25 ms off respectively, hence two different issues in one object.
|
||||
var hitObjects = new List<HitObject>
|
||||
{
|
||||
getSliderMock(startTime: 98, endTime: 398.75d).Object
|
||||
};
|
||||
|
||||
var issues = check.Run(getContext(hitObjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap));
|
||||
}
|
||||
|
||||
private Mock<Slider> getSliderMock(double startTime, double endTime, int repeats = 0)
|
||||
{
|
||||
var mockSlider = new Mock<Slider>();
|
||||
mockSlider.SetupGet(s => s.StartTime).Returns(startTime);
|
||||
mockSlider.As<IHasRepeats>().Setup(r => r.RepeatCount).Returns(repeats);
|
||||
mockSlider.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
|
||||
|
||||
return mockSlider;
|
||||
}
|
||||
|
||||
private void assertOk(List<HitObject> hitObjects)
|
||||
{
|
||||
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
||||
}
|
||||
|
||||
private void assert1Ms(List<HitObject> hitObjects, int count = 1)
|
||||
{
|
||||
var issues = check.Run(getContext(hitObjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(count));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap));
|
||||
}
|
||||
|
||||
private void assert2Ms(List<HitObject> hitObjects, int count = 1)
|
||||
{
|
||||
var issues = check.Run(getContext(hitObjects)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(count));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap));
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(List<HitObject> hitObjects)
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
ControlPointInfo = cpi,
|
||||
HitObjects = hitObjects
|
||||
};
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
}
|
||||
}
|
||||
}
|
@ -146,7 +146,7 @@ namespace osu.Game.Tests.Mods
|
||||
if (isValid)
|
||||
Assert.IsNull(invalid);
|
||||
else
|
||||
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
}
|
||||
|
||||
public abstract class CustomMod1 : Mod
|
||||
|
91
osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs
Normal file
91
osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs
Normal file
@ -0,0 +1,91 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public class ClosestBeatDivisorTest
|
||||
{
|
||||
[Test]
|
||||
public void TestExactDivisors()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
|
||||
|
||||
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
|
||||
|
||||
assertClosestDivisors(divisors, divisors, cpi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExactDivisorWithTempoChanges()
|
||||
{
|
||||
int offset = 0;
|
||||
int[] beatLengths = { 1000, 200, 100, 50 };
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
|
||||
foreach (int beatLength in beatLengths)
|
||||
{
|
||||
cpi.Add(offset, new TimingControlPoint { BeatLength = beatLength });
|
||||
offset += beatLength * 2;
|
||||
}
|
||||
|
||||
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3 };
|
||||
|
||||
assertClosestDivisors(divisors, divisors, cpi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExactDivisorsHighBPMStream()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
|
||||
|
||||
// A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors.
|
||||
double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 };
|
||||
double[] closestDivisors = { 4, 2, 4, 1, 4, 2, 4, 1 };
|
||||
|
||||
assertClosestDivisors(divisors, closestDivisors, cpi, step: 1 / 4d);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApproximateDivisors()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
|
||||
|
||||
double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 };
|
||||
double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
|
||||
|
||||
assertClosestDivisors(divisors, closestDivisors, cpi);
|
||||
}
|
||||
|
||||
private void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1)
|
||||
{
|
||||
List<HitObject> hitobjects = new List<HitObject>();
|
||||
double offset = cpi.TimingPoints[0].Time;
|
||||
|
||||
for (int i = 0; i < divisors.Count; ++i)
|
||||
{
|
||||
double beatLength = cpi.TimingPointAt(offset).BeatLength;
|
||||
hitobjects.Add(new HitObject { StartTime = offset + beatLength / divisors[i] });
|
||||
offset += beatLength * step;
|
||||
}
|
||||
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = hitobjects,
|
||||
ControlPointInfo = cpi
|
||||
};
|
||||
|
||||
for (int i = 0; i < divisors.Count; ++i)
|
||||
Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.GetClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}");
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@ -278,6 +279,54 @@ namespace osu.Game.Tests.NonVisual
|
||||
setTime(-100, -100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReplayFramesSortStability()
|
||||
{
|
||||
const double repeating_time = 5000;
|
||||
|
||||
// add a collection of frames in shuffled order time-wise; each frame also stores its original index to check stability later.
|
||||
// data is hand-picked and breaks if the unstable List<T>.Sort() is used.
|
||||
// in theory this can still return a false-positive with another unstable algorithm if extremely unlucky,
|
||||
// but there is no conceivable fool-proof way to prevent that anyways.
|
||||
replay.Frames.AddRange(new[]
|
||||
{
|
||||
repeating_time,
|
||||
0,
|
||||
3000,
|
||||
repeating_time,
|
||||
repeating_time,
|
||||
6000,
|
||||
9000,
|
||||
repeating_time,
|
||||
repeating_time,
|
||||
1000,
|
||||
11000,
|
||||
21000,
|
||||
4000,
|
||||
repeating_time,
|
||||
repeating_time,
|
||||
8000,
|
||||
2000,
|
||||
7000,
|
||||
repeating_time,
|
||||
repeating_time,
|
||||
10000
|
||||
}.Select((time, index) => new TestReplayFrame(time, true, index)));
|
||||
|
||||
replay.HasReceivedAllFrames = true;
|
||||
|
||||
// create a new handler with the replay for the sort to be performed.
|
||||
handler = new TestInputHandler(replay);
|
||||
|
||||
// ensure sort stability by checking that the frames with time == repeating_time are sorted in ascending frame index order themselves.
|
||||
var repeatingTimeFramesData = replay.Frames
|
||||
.Cast<TestReplayFrame>()
|
||||
.Where(f => f.Time == repeating_time)
|
||||
.Select(f => f.FrameIndex);
|
||||
|
||||
Assert.That(repeatingTimeFramesData, Is.Ordered.Ascending);
|
||||
}
|
||||
|
||||
private void setReplayFrames()
|
||||
{
|
||||
replay.Frames = new List<ReplayFrame>
|
||||
@ -324,11 +373,13 @@ namespace osu.Game.Tests.NonVisual
|
||||
private class TestReplayFrame : ReplayFrame
|
||||
{
|
||||
public readonly bool IsImportant;
|
||||
public readonly int FrameIndex;
|
||||
|
||||
public TestReplayFrame(double time, bool isImportant = false)
|
||||
public TestReplayFrame(double time, bool isImportant = false, int frameIndex = 0)
|
||||
: base(time)
|
||||
{
|
||||
IsImportant = isImportant;
|
||||
FrameIndex = frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
@ -34,7 +35,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
||||
changeState(6, MultiplayerUserState.WaitingForLoad);
|
||||
checkPlayingUserCount(6);
|
||||
|
||||
AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User));
|
||||
AddStep("another user left", () => Client.RemoveUser((Client.Room?.Users.Last().User).AsNonNull()));
|
||||
checkPlayingUserCount(5);
|
||||
|
||||
AddStep("leave room", () => Client.LeaveRoom());
|
||||
@ -53,9 +54,9 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
||||
Client.RoomSetupAction = room =>
|
||||
{
|
||||
room.State = MultiplayerRoomState.Playing;
|
||||
room.Users.Add(new MultiplayerRoomUser(55)
|
||||
room.Users.Add(new MultiplayerRoomUser(PLAYER_1_ID)
|
||||
{
|
||||
User = new User { Id = 55 },
|
||||
User = new User { Id = PLAYER_1_ID },
|
||||
State = MultiplayerUserState.Playing
|
||||
});
|
||||
};
|
||||
|
223
osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
Normal file
223
osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
Normal file
@ -0,0 +1,223 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.OnlinePlay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneCatchUpSyncManager : OsuTestScene
|
||||
{
|
||||
private TestManualClock master;
|
||||
private CatchUpSyncManager syncManager;
|
||||
|
||||
private TestSpectatorPlayerClock player1;
|
||||
private TestSpectatorPlayerClock player2;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
syncManager = new CatchUpSyncManager(master = new TestManualClock());
|
||||
syncManager.AddPlayerClock(player1 = new TestSpectatorPlayerClock(1));
|
||||
syncManager.AddPlayerClock(player2 = new TestSpectatorPlayerClock(2));
|
||||
|
||||
Schedule(() => Child = syncManager);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames()
|
||||
{
|
||||
setWaiting(() => player1, false);
|
||||
assertMasterState(false);
|
||||
assertPlayerClockState(() => player1, false);
|
||||
assertPlayerClockState(() => player2, false);
|
||||
|
||||
setWaiting(() => player2, false);
|
||||
assertMasterState(true);
|
||||
assertPlayerClockState(() => player1, true);
|
||||
assertPlayerClockState(() => player2, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime()
|
||||
{
|
||||
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
|
||||
assertMasterState(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
|
||||
{
|
||||
setWaiting(() => player1, false);
|
||||
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
|
||||
assertMasterState(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockDoesNotCatchUpWhenSlightlyOutOfSync()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1);
|
||||
assertCatchingUp(() => player1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockStartsCatchingUpWhenTooFarBehind()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
|
||||
assertCatchingUp(() => player1, true);
|
||||
assertCatchingUp(() => player2, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockKeepsCatchingUpWhenSlightlyOutOfSync()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
|
||||
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET + 1);
|
||||
assertCatchingUp(() => player1, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockStopsCatchingUpWhenInSync()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2);
|
||||
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET);
|
||||
assertCatchingUp(() => player1, false);
|
||||
assertCatchingUp(() => player2, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockDoesNotStopWhenSlightlyAhead()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET);
|
||||
assertCatchingUp(() => player1, false);
|
||||
assertPlayerClockState(() => player1, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerClockStopsWhenTooFarAheadAndStartsWhenBackInSync()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET - 1);
|
||||
|
||||
// This is a silent catchup, where IsCatchingUp = false but IsRunning = false also.
|
||||
assertCatchingUp(() => player1, false);
|
||||
assertPlayerClockState(() => player1, false);
|
||||
|
||||
setMasterTime(1);
|
||||
assertCatchingUp(() => player1, false);
|
||||
assertPlayerClockState(() => player1, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInSyncPlayerClockDoesNotStartIfWaitingOnFrames()
|
||||
{
|
||||
setAllWaiting(false);
|
||||
|
||||
assertPlayerClockState(() => player1, true);
|
||||
setWaiting(() => player1, true);
|
||||
assertPlayerClockState(() => player1, false);
|
||||
}
|
||||
|
||||
private void setWaiting(Func<TestSpectatorPlayerClock> playerClock, bool waiting)
|
||||
=> AddStep($"set player clock {playerClock().Id} waiting = {waiting}", () => playerClock().WaitingOnFrames.Value = waiting);
|
||||
|
||||
private void setAllWaiting(bool waiting) => AddStep($"set all player clocks waiting = {waiting}", () =>
|
||||
{
|
||||
player1.WaitingOnFrames.Value = waiting;
|
||||
player2.WaitingOnFrames.Value = waiting;
|
||||
});
|
||||
|
||||
private void setMasterTime(double time)
|
||||
=> AddStep($"set master = {time}", () => master.Seek(time));
|
||||
|
||||
/// <summary>
|
||||
/// clock.Time = master.Time - offsetFromMaster
|
||||
/// </summary>
|
||||
private void setPlayerClockTime(Func<TestSpectatorPlayerClock> playerClock, double offsetFromMaster)
|
||||
=> AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster));
|
||||
|
||||
private void assertMasterState(bool running)
|
||||
=> AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
|
||||
|
||||
private void assertCatchingUp(Func<TestSpectatorPlayerClock> playerClock, bool catchingUp) =>
|
||||
AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp);
|
||||
|
||||
private void assertPlayerClockState(Func<TestSpectatorPlayerClock> playerClock, bool running)
|
||||
=> AddAssert($"player clock {playerClock().Id} {(running ? "is" : "is not")} running", () => playerClock().IsRunning == running);
|
||||
|
||||
private class TestSpectatorPlayerClock : TestManualClock, ISpectatorPlayerClock
|
||||
{
|
||||
public Bindable<bool> WaitingOnFrames { get; } = new Bindable<bool>(true);
|
||||
|
||||
public bool IsCatchingUp { get; set; }
|
||||
|
||||
public IFrameBasedClock Source
|
||||
{
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public readonly int Id;
|
||||
|
||||
public TestSpectatorPlayerClock(int id)
|
||||
{
|
||||
Id = id;
|
||||
|
||||
WaitingOnFrames.BindValueChanged(waiting =>
|
||||
{
|
||||
if (waiting.NewValue)
|
||||
Stop();
|
||||
else
|
||||
Start();
|
||||
});
|
||||
}
|
||||
|
||||
public void ProcessFrame()
|
||||
{
|
||||
}
|
||||
|
||||
public double ElapsedFrameTime => 0;
|
||||
|
||||
public double FramesPerSecond => 0;
|
||||
|
||||
public FrameTimeInfo TimeInfo => default;
|
||||
}
|
||||
|
||||
private class TestManualClock : ManualClock, IAdjustableClock
|
||||
{
|
||||
public void Start() => IsRunning = true;
|
||||
|
||||
public void Stop() => IsRunning = false;
|
||||
|
||||
public bool Seek(double position)
|
||||
{
|
||||
CurrentTime = position;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public void ResetSpeedAdjustments()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
private BlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<BlueprintContainer>().First();
|
||||
private EditorBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestSelectedObjectHasPriorityWhenOverlapping()
|
||||
|
@ -1,45 +1,50 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneComposeSelectBox : OsuTestScene
|
||||
public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Container selectionArea;
|
||||
private SelectionBox selectionBox;
|
||||
|
||||
public TestSceneComposeSelectBox()
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SelectionBox selectionBox = null;
|
||||
|
||||
AddStep("create box", () =>
|
||||
Child = selectionArea = new Container
|
||||
Child = selectionArea = new Container
|
||||
{
|
||||
Size = new Vector2(400),
|
||||
Position = -new Vector2(150),
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Size = new Vector2(400),
|
||||
Position = -new Vector2(150),
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
selectionBox = new SelectionBox
|
||||
{
|
||||
selectionBox = new SelectionBox
|
||||
{
|
||||
CanRotate = true,
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
||||
OnRotation = handleRotation,
|
||||
OnScale = handleScale
|
||||
}
|
||||
CanRotate = true,
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
|
||||
OnRotation = handleRotation,
|
||||
OnScale = handleScale
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
|
||||
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
|
||||
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
|
||||
}
|
||||
InputManager.MoveMouseTo(selectionBox);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
private bool handleScale(Vector2 amount, Anchor reference)
|
||||
{
|
||||
@ -68,5 +73,115 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
selectionArea.Rotation += angle;
|
||||
return true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRotationHandleShownOnHover()
|
||||
{
|
||||
SelectionBoxRotationHandle rotationHandle = null;
|
||||
|
||||
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
|
||||
|
||||
AddAssert("handle hidden", () => rotationHandle.Alpha == 0);
|
||||
AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle));
|
||||
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
|
||||
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRotationHandleShownOnHoveringClosestScaleHandler()
|
||||
{
|
||||
SelectionBoxRotationHandle rotationHandle = null;
|
||||
|
||||
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
|
||||
|
||||
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
AddStep("hover over closest scale handle", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
|
||||
});
|
||||
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
|
||||
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverRotationHandleFromScaleHandle()
|
||||
{
|
||||
SelectionBoxRotationHandle rotationHandle = null;
|
||||
|
||||
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
|
||||
|
||||
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
AddStep("hover over closest scale handle", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
|
||||
});
|
||||
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
|
||||
AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered);
|
||||
|
||||
AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle));
|
||||
AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1);
|
||||
AddAssert("rotation handle hovered", () => rotationHandle.IsHovered);
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
|
||||
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldingScaleHandleHidesCorrespondingRotationHandle()
|
||||
{
|
||||
SelectionBoxRotationHandle rotationHandle = null;
|
||||
|
||||
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
|
||||
|
||||
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
AddStep("hover over closest scale handle", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
|
||||
});
|
||||
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
|
||||
AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
|
||||
int i;
|
||||
ScheduledDelegate mouseMove = null;
|
||||
|
||||
AddStep("start dragging", () =>
|
||||
{
|
||||
i = 0;
|
||||
|
||||
mouseMove = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i));
|
||||
}, 100, true);
|
||||
});
|
||||
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
|
||||
|
||||
AddStep("end dragging", () => mouseMove.Cancel());
|
||||
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
|
||||
AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
|
||||
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that hovering over two handles instantaneously from one to another does not crash or cause issues to the visibility state.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHoverOverTwoHandlesInstantaneously()
|
||||
{
|
||||
AddStep("hover over top-left scale handle", () =>
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopLeft)));
|
||||
AddStep("hover over top-right scale handle", () =>
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == Anchor.TopRight)));
|
||||
AddUntilStep("top-left rotation handle hidden", () =>
|
||||
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopLeft).Alpha == 0);
|
||||
AddUntilStep("top-right rotation handle shown", () =>
|
||||
this.ChildrenOfType<SelectionBoxRotationHandle>().Single(r => r.Anchor == Anchor.TopRight).Alpha == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +132,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear());
|
||||
|
||||
AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<SelectionHandler>().First().Alpha == 0);
|
||||
AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<SelectionHandler>().First().Alpha == 0);
|
||||
AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<SelectionBox>().First().Alpha == 0);
|
||||
AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<SelectionBox>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
@ -142,8 +142,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000);
|
||||
|
||||
AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<SelectionHandler>().First().Alpha > 0);
|
||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<SelectionHandler>().First().Alpha > 0);
|
||||
AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -26,15 +26,15 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
private BlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<BlueprintContainer>().First();
|
||||
private EditorBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
|
||||
|
||||
private void moveMouseToObject(Func<HitObject> targetFunc)
|
||||
{
|
||||
AddStep("move mouse to object", () =>
|
||||
{
|
||||
var pos = blueprintContainer.SelectionBlueprints
|
||||
.First(s => s.HitObject == targetFunc())
|
||||
.First(s => s.Item == targetFunc())
|
||||
.ChildrenOfType<HitCirclePiece>()
|
||||
.First().ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
}));
|
||||
|
||||
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
||||
@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
};
|
||||
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||
@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
}));
|
||||
|
||||
moveMouseToObject(() => addedObjects[0]);
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private void seekToBreak(int breakIndex)
|
||||
{
|
||||
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
|
||||
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
|
||||
AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime);
|
||||
|
||||
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
|
||||
}
|
||||
|
@ -1,47 +1,35 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneComboCounter : SkinnableTestScene
|
||||
{
|
||||
private IEnumerable<SkinnableComboCounter> comboCounters => CreatedDrawables.OfType<SkinnableComboCounter>();
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create combo counters", () => SetContents(() =>
|
||||
{
|
||||
var comboCounter = new SkinnableComboCounter();
|
||||
comboCounter.Current.Value = 1;
|
||||
return comboCounter;
|
||||
}));
|
||||
AddStep("Create combo counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ComboCounter))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestComboCounterIncrementing()
|
||||
{
|
||||
AddRepeatStep("increase combo", () =>
|
||||
{
|
||||
foreach (var counter in comboCounters)
|
||||
counter.Current.Value++;
|
||||
}, 10);
|
||||
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
|
||||
|
||||
AddStep("reset combo", () =>
|
||||
{
|
||||
foreach (var counter in comboCounters)
|
||||
counter.Current.Value = 0;
|
||||
});
|
||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -20,24 +23,28 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
private void create(HealthProcessor healthProcessor)
|
||||
{
|
||||
AddStep("create layer", () =>
|
||||
{
|
||||
Child = layer = new FailingLayer();
|
||||
layer.BindHealthProcessor(new DrainingHealthProcessor(1));
|
||||
Child = new HealthProcessorContainer(healthProcessor)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = layer = new FailingLayer()
|
||||
};
|
||||
|
||||
layer.ShowHealth.BindTo(showHealth);
|
||||
});
|
||||
|
||||
AddStep("show health", () => showHealth.Value = true);
|
||||
AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
|
||||
AddUntilStep("layer is visible", () => layer.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLayerFading()
|
||||
{
|
||||
create(new DrainingHealthProcessor(0));
|
||||
|
||||
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
|
||||
{
|
||||
if (layer != null)
|
||||
@ -45,14 +52,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
||||
AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f);
|
||||
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
|
||||
AddStep("set health to 1", () => layer.Current.Value = 1f);
|
||||
AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent);
|
||||
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLayerDisabledViaConfig()
|
||||
{
|
||||
create(new DrainingHealthProcessor(0));
|
||||
AddUntilStep("layer is visible", () => layer.IsPresent);
|
||||
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||
@ -61,7 +70,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestLayerVisibilityWithAccumulatingProcessor()
|
||||
{
|
||||
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1)));
|
||||
create(new AccumulatingHealthProcessor(1));
|
||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||
}
|
||||
@ -69,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestLayerVisibilityWithDrainingProcessor()
|
||||
{
|
||||
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1)));
|
||||
create(new DrainingHealthProcessor(0));
|
||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
||||
AddWaitStep("wait for potential fade", 10);
|
||||
AddAssert("layer is still visible", () => layer.IsPresent);
|
||||
@ -78,6 +88,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestLayerVisibilityWithDifferentOptions()
|
||||
{
|
||||
create(new DrainingHealthProcessor(0));
|
||||
|
||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
||||
|
||||
AddStep("don't show health", () => showHealth.Value = false);
|
||||
@ -96,5 +108,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
|
||||
AddUntilStep("layer fade is visible", () => layer.IsPresent);
|
||||
}
|
||||
|
||||
private class HealthProcessorContainer : Container
|
||||
{
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private readonly HealthProcessor healthProcessor;
|
||||
|
||||
public HealthProcessorContainer(HealthProcessor healthProcessor)
|
||||
{
|
||||
this.healthProcessor = healthProcessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -19,6 +20,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||
@ -31,9 +38,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
createNew();
|
||||
|
||||
AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10);
|
||||
AddRepeatStep("increase combo", () => { scoreProcessor.Combo.Value++; }, 10);
|
||||
|
||||
AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; });
|
||||
AddStep("reset combo", () => { scoreProcessor.Combo.Value = 0; });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -139,12 +146,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("create overlay", () =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
||||
|
||||
hudOverlay.ComboCounter.Current.Value = 1;
|
||||
scoreProcessor.Combo.Value = 1;
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
|
@ -2,15 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
@ -20,14 +21,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneHitErrorMeter : OsuTestScene
|
||||
{
|
||||
private BarHitErrorMeter barMeter;
|
||||
private BarHitErrorMeter barMeter2;
|
||||
private BarHitErrorMeter barMeter3;
|
||||
private ColourHitErrorMeter colourMeter;
|
||||
private ColourHitErrorMeter colourMeter2;
|
||||
private ColourHitErrorMeter colourMeter3;
|
||||
private HitWindows hitWindows;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
public TestSceneHitErrorMeter()
|
||||
{
|
||||
recreateDisplay(new OsuHitWindows(), 5);
|
||||
@ -105,40 +103,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
});
|
||||
|
||||
Add(barMeter = new BarHitErrorMeter(hitWindows, true)
|
||||
Add(new BarHitErrorMeter(hitWindows, true)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
});
|
||||
|
||||
Add(barMeter2 = new BarHitErrorMeter(hitWindows, false)
|
||||
Add(new BarHitErrorMeter(hitWindows, false)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
});
|
||||
|
||||
Add(barMeter3 = new BarHitErrorMeter(hitWindows, true)
|
||||
Add(new BarHitErrorMeter(hitWindows, true)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Rotation = 270,
|
||||
});
|
||||
|
||||
Add(colourMeter = new ColourHitErrorMeter(hitWindows)
|
||||
Add(new ColourHitErrorMeter(hitWindows)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Margin = new MarginPadding { Right = 50 }
|
||||
});
|
||||
|
||||
Add(colourMeter2 = new ColourHitErrorMeter(hitWindows)
|
||||
Add(new ColourHitErrorMeter(hitWindows)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 50 }
|
||||
});
|
||||
|
||||
Add(colourMeter3 = new ColourHitErrorMeter(hitWindows)
|
||||
Add(new ColourHitErrorMeter(hitWindows)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -149,18 +147,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void newJudgement(double offset = 0)
|
||||
{
|
||||
var judgement = new JudgementResult(new HitObject(), new Judgement())
|
||||
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
|
||||
{
|
||||
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
||||
Type = HitResult.Perfect,
|
||||
};
|
||||
|
||||
barMeter.OnNewJudgement(judgement);
|
||||
barMeter2.OnNewJudgement(judgement);
|
||||
barMeter3.OnNewJudgement(judgement);
|
||||
colourMeter.OnNewJudgement(judgement);
|
||||
colourMeter2.OnNewJudgement(judgement);
|
||||
colourMeter3.OnNewJudgement(judgement);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
Normal file
45
osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
Normal file
@ -0,0 +1,45 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Editor;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditor : PlayerTestScene
|
||||
{
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; }
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reload skin editor", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning.Editor;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditorComponentsList : SkinnableTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddStep("show available components", () => SetContents(() => new SkinComponentToolbox(300)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
}));
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning.Editor;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create editor overlay", () =>
|
||||
{
|
||||
SetContents(() =>
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var mods = new[] { ruleset.GetAutoplayMod() };
|
||||
var working = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
|
||||
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods);
|
||||
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, mods)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
||||
scoreProcessor.Combo.Value = 1;
|
||||
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
drawableRuleset,
|
||||
hudOverlay,
|
||||
new SkinEditor(hudOverlay),
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
}
|
||||
}
|
@ -1,49 +1,36 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene
|
||||
{
|
||||
private IEnumerable<SkinnableAccuracyCounter> accuracyCounters => CreatedDrawables.OfType<SkinnableAccuracyCounter>();
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create combo counters", () => SetContents(() =>
|
||||
{
|
||||
var accuracyCounter = new SkinnableAccuracyCounter();
|
||||
|
||||
accuracyCounter.Current.Value = 1;
|
||||
|
||||
return accuracyCounter;
|
||||
}));
|
||||
AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1);
|
||||
AddStep("Create accuracy counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangingAccuracy()
|
||||
{
|
||||
AddStep(@"Reset all", delegate
|
||||
{
|
||||
foreach (var s in accuracyCounters)
|
||||
s.Current.Value = 1;
|
||||
});
|
||||
AddStep(@"Reset all", () => scoreProcessor.Accuracy.Value = 1);
|
||||
|
||||
AddStep(@"Hit! :D", delegate
|
||||
{
|
||||
foreach (var s in accuracyCounters)
|
||||
s.Current.Value -= 0.023f;
|
||||
});
|
||||
AddStep(@"Miss :(", () => scoreProcessor.Accuracy.Value -= 0.023);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -23,6 +24,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||
|
||||
// best way to check without exposing.
|
||||
@ -37,17 +44,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
createNew();
|
||||
|
||||
AddRepeatStep("increase combo", () =>
|
||||
{
|
||||
foreach (var hud in hudOverlays)
|
||||
hud.ComboCounter.Current.Value++;
|
||||
}, 10);
|
||||
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
|
||||
|
||||
AddStep("reset combo", () =>
|
||||
{
|
||||
foreach (var hud in hudOverlays)
|
||||
hud.ComboCounter.Current.Value = 0;
|
||||
});
|
||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -80,13 +79,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
SetContents(() =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
||||
|
||||
hudOverlay.ComboCounter.Current.Value = 1;
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
return hudOverlay;
|
||||
|
@ -1,35 +1,33 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinnableHealthDisplay : SkinnableTestScene
|
||||
{
|
||||
private IEnumerable<SkinnableHealthDisplay> healthDisplays => CreatedDrawables.OfType<SkinnableHealthDisplay>();
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create health displays", () =>
|
||||
{
|
||||
SetContents(() => new SkinnableHealthDisplay());
|
||||
});
|
||||
AddStep("Create health displays", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.HealthDisplay))));
|
||||
AddStep(@"Reset all", delegate
|
||||
{
|
||||
foreach (var s in healthDisplays)
|
||||
s.Current.Value = 1;
|
||||
healthProcessor.Health.Value = 1;
|
||||
});
|
||||
}
|
||||
|
||||
@ -38,23 +36,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddRepeatStep(@"decrease hp", delegate
|
||||
{
|
||||
foreach (var healthDisplay in healthDisplays)
|
||||
healthDisplay.Current.Value -= 0.08f;
|
||||
healthProcessor.Health.Value -= 0.08f;
|
||||
}, 10);
|
||||
|
||||
AddRepeatStep(@"increase hp without flash", delegate
|
||||
{
|
||||
foreach (var healthDisplay in healthDisplays)
|
||||
healthDisplay.Current.Value += 0.1f;
|
||||
healthProcessor.Health.Value += 0.1f;
|
||||
}, 3);
|
||||
|
||||
AddRepeatStep(@"increase hp with flash", delegate
|
||||
{
|
||||
foreach (var healthDisplay in healthDisplays)
|
||||
healthProcessor.Health.Value += 0.1f;
|
||||
healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement())
|
||||
{
|
||||
healthDisplay.Current.Value += 0.1f;
|
||||
healthDisplay.Flash(new JudgementResult(null, new OsuJudgement()));
|
||||
}
|
||||
Type = HitResult.Perfect
|
||||
});
|
||||
}, 3);
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +1,41 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinnableScoreCounter : SkinnableTestScene
|
||||
{
|
||||
private IEnumerable<SkinnableScoreCounter> scoreCounters => CreatedDrawables.OfType<SkinnableScoreCounter>();
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create combo counters", () => SetContents(() =>
|
||||
{
|
||||
var comboCounter = new SkinnableScoreCounter();
|
||||
comboCounter.Current.Value = 1;
|
||||
return comboCounter;
|
||||
}));
|
||||
AddStep("Create score counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ScoreCounter))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreCounterIncrementing()
|
||||
{
|
||||
AddStep(@"Reset all", delegate
|
||||
{
|
||||
foreach (var s in scoreCounters)
|
||||
s.Current.Value = 0;
|
||||
});
|
||||
AddStep(@"Reset all", () => scoreProcessor.TotalScore.Value = 0);
|
||||
|
||||
AddStep(@"Hit! :D", delegate
|
||||
{
|
||||
foreach (var s in scoreCounters)
|
||||
s.Current.Value += 300;
|
||||
});
|
||||
AddStep(@"Hit! :D", () => scoreProcessor.TotalScore.Value += 300);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVeryLargeScore()
|
||||
{
|
||||
AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000));
|
||||
AddStep("set large score", () => scoreProcessor.TotalScore.Value = 1_000_000_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,32 @@
|
||||
// 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 System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSpectator : ScreenTestScene
|
||||
{
|
||||
private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" };
|
||||
|
||||
[Cached(typeof(SpectatorStreamingClient))]
|
||||
private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient();
|
||||
|
||||
@ -214,9 +212,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||
|
||||
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(beatmapId ?? importedBeatmapId));
|
||||
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||
|
||||
private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(beatmapId ?? importedBeatmapId));
|
||||
private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||
|
||||
private void checkPaused(bool state) =>
|
||||
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
||||
@ -225,89 +223,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("send frames", () =>
|
||||
{
|
||||
testSpectatorStreamingClient.SendFrames(nextFrame, count);
|
||||
testSpectatorStreamingClient.SendFrames(streamingUser.Id, nextFrame, count);
|
||||
nextFrame += count;
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSpectatingScreen()
|
||||
{
|
||||
AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(testSpectatorStreamingClient.StreamingUser)));
|
||||
AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
|
||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
|
||||
}
|
||||
|
||||
public class TestSpectatorStreamingClient : SpectatorStreamingClient
|
||||
{
|
||||
public readonly User StreamingUser = new User { Id = 55, Username = "Test user" };
|
||||
|
||||
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
||||
|
||||
private int beatmapId;
|
||||
|
||||
public TestSpectatorStreamingClient()
|
||||
: base(new DevelopmentEndpointConfiguration())
|
||||
{
|
||||
}
|
||||
|
||||
public void StartPlay(int beatmapId)
|
||||
{
|
||||
this.beatmapId = beatmapId;
|
||||
sendState(beatmapId);
|
||||
}
|
||||
|
||||
public void EndPlay(int beatmapId)
|
||||
{
|
||||
((ISpectatorClient)this).UserFinishedPlaying(StreamingUser.Id, new SpectatorState
|
||||
{
|
||||
BeatmapID = beatmapId,
|
||||
RulesetID = 0,
|
||||
});
|
||||
|
||||
sentState = false;
|
||||
}
|
||||
|
||||
private bool sentState;
|
||||
|
||||
public void SendFrames(int index, int count)
|
||||
{
|
||||
var frames = new List<LegacyReplayFrame>();
|
||||
|
||||
for (int i = index; i < index + count; i++)
|
||||
{
|
||||
var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1;
|
||||
|
||||
frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
|
||||
}
|
||||
|
||||
var bundle = new FrameDataBundle(new ScoreInfo(), frames);
|
||||
((ISpectatorClient)this).UserSentFrames(StreamingUser.Id, bundle);
|
||||
|
||||
if (!sentState)
|
||||
sendState(beatmapId);
|
||||
}
|
||||
|
||||
public override void WatchUser(int userId)
|
||||
{
|
||||
if (!PlayingUsers.Contains(userId) && sentState)
|
||||
{
|
||||
// usually the server would do this.
|
||||
sendState(beatmapId);
|
||||
}
|
||||
|
||||
base.WatchUser(userId);
|
||||
}
|
||||
|
||||
private void sendState(int beatmapId)
|
||||
{
|
||||
sentState = true;
|
||||
((ISpectatorClient)this).UserBeganPlaying(StreamingUser.Id, new SpectatorState
|
||||
{
|
||||
BeatmapID = beatmapId,
|
||||
RulesetID = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestUserLookupCache : UserLookupCache
|
||||
{
|
||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User
|
||||
|
201
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
Normal file
201
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneStoryboardWithOutro : PlayerTestScene
|
||||
{
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
||||
|
||||
private double currentStoryboardDuration;
|
||||
|
||||
private bool showResults = true;
|
||||
|
||||
private event Func<HealthProcessor, JudgementResult, bool> currentFailConditions;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
||||
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
||||
AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false);
|
||||
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
|
||||
AddStep("set ShowResults = true", () => showResults = true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardSkipOutro()
|
||||
{
|
||||
CreateTest(null);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||
AddAssert("score shown", () => Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardNoSkipOutro()
|
||||
{
|
||||
CreateTest(null);
|
||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardExitToSkipOutro()
|
||||
{
|
||||
CreateTest(null);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddStep("exit via pause", () => Player.ExitViaPause());
|
||||
AddAssert("score shown", () => Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestStoryboardToggle(bool enabledAtBeginning)
|
||||
{
|
||||
CreateTest(null);
|
||||
AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning));
|
||||
AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning));
|
||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutroEndsDuringFailAnimation()
|
||||
{
|
||||
CreateTest(() =>
|
||||
{
|
||||
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
|
||||
AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300);
|
||||
});
|
||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowResultsFalse()
|
||||
{
|
||||
CreateTest(() =>
|
||||
{
|
||||
AddStep("set ShowResults = false", () => showResults = false);
|
||||
});
|
||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||
AddWaitStep("wait", 10);
|
||||
AddAssert("no score shown", () => !Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardEndsBeforeCompletion()
|
||||
{
|
||||
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
|
||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardRewind()
|
||||
{
|
||||
SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType<SkipOverlay.FadeContainer>().First();
|
||||
|
||||
CreateTest(null);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
||||
|
||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-1000));
|
||||
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
|
||||
|
||||
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerformExitNoOutro()
|
||||
{
|
||||
CreateTest(null);
|
||||
AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false));
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddStep("exit via pause", () => Player.ExitViaPause());
|
||||
AddAssert("player exited", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
protected override bool AllowFail => true;
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer(currentFailConditions, showResults);
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap();
|
||||
beatmap.HitObjects.Add(new HitCircle());
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
return base.CreateWorkingBeatmap(beatmap, createStoryboard(currentStoryboardDuration));
|
||||
}
|
||||
|
||||
private Storyboard createStoryboard(double duration)
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0);
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
return storyboard;
|
||||
}
|
||||
|
||||
protected class OutroPlayer : TestPlayer
|
||||
{
|
||||
public void ExitViaPause() => PerformExit(true);
|
||||
|
||||
public new FailOverlay FailOverlay => base.FailOverlay;
|
||||
|
||||
public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen;
|
||||
|
||||
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
||||
|
||||
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
||||
: base(false, showResults)
|
||||
{
|
||||
this.failConditions = failConditions;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
HealthProcessor.FailConditions += failConditions;
|
||||
}
|
||||
|
||||
protected override Task ImportScore(Score score)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,20 +11,17 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerSpectatorLeaderboard : MultiplayerTestScene
|
||||
public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
|
||||
{
|
||||
[Cached(typeof(SpectatorStreamingClient))]
|
||||
private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient();
|
||||
@ -37,11 +34,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private readonly Dictionary<int, ManualClock> clocks = new Dictionary<int, ManualClock>
|
||||
{
|
||||
{ 55, new ManualClock() },
|
||||
{ 56, new ManualClock() }
|
||||
{ PLAYER_1_ID, new ManualClock() },
|
||||
{ PLAYER_2_ID, new ManualClock() }
|
||||
};
|
||||
|
||||
public TestSceneMultiplayerSpectatorLeaderboard()
|
||||
public TestSceneMultiSpectatorLeaderboard()
|
||||
{
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
@ -54,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[SetUpSteps]
|
||||
public new void SetUpSteps()
|
||||
{
|
||||
MultiplayerSpectatorLeaderboard leaderboard = null;
|
||||
MultiSpectatorLeaderboard leaderboard = null;
|
||||
|
||||
AddStep("reset", () =>
|
||||
{
|
||||
@ -78,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
var scoreProcessor = new OsuScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(playable);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||
@ -95,46 +92,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
AddStep("send frames", () =>
|
||||
{
|
||||
// For user 55, send frames in sets of 1.
|
||||
// For user 56, send frames in sets of 10.
|
||||
// For player 1, send frames in sets of 1.
|
||||
// For player 2, send frames in sets of 10.
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
streamingClient.SendFrames(55, i, 1);
|
||||
streamingClient.SendFrames(PLAYER_1_ID, i, 1);
|
||||
|
||||
if (i % 10 == 0)
|
||||
streamingClient.SendFrames(56, i, 10);
|
||||
streamingClient.SendFrames(PLAYER_2_ID, i, 10);
|
||||
}
|
||||
});
|
||||
|
||||
assertCombo(55, 1);
|
||||
assertCombo(56, 10);
|
||||
assertCombo(PLAYER_1_ID, 1);
|
||||
assertCombo(PLAYER_2_ID, 10);
|
||||
|
||||
// Advance to a point where only user 55's frame changes.
|
||||
// Advance to a point where only user player 1's frame changes.
|
||||
setTime(500);
|
||||
assertCombo(55, 5);
|
||||
assertCombo(56, 10);
|
||||
assertCombo(PLAYER_1_ID, 5);
|
||||
assertCombo(PLAYER_2_ID, 10);
|
||||
|
||||
// Advance to a point where both user's frame changes.
|
||||
setTime(1100);
|
||||
assertCombo(55, 11);
|
||||
assertCombo(56, 20);
|
||||
assertCombo(PLAYER_1_ID, 11);
|
||||
assertCombo(PLAYER_2_ID, 20);
|
||||
|
||||
// Advance user 56 only to a point where its frame changes.
|
||||
setTime(56, 2100);
|
||||
assertCombo(55, 11);
|
||||
assertCombo(56, 30);
|
||||
// Advance user player 2 only to a point where its frame changes.
|
||||
setTime(PLAYER_2_ID, 2100);
|
||||
assertCombo(PLAYER_1_ID, 11);
|
||||
assertCombo(PLAYER_2_ID, 30);
|
||||
|
||||
// Advance both users beyond their last frame
|
||||
setTime(101 * 100);
|
||||
assertCombo(55, 100);
|
||||
assertCombo(56, 100);
|
||||
assertCombo(PLAYER_1_ID, 100);
|
||||
assertCombo(PLAYER_2_ID, 100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoFrames()
|
||||
{
|
||||
assertCombo(55, 0);
|
||||
assertCombo(56, 0);
|
||||
assertCombo(PLAYER_1_ID, 0);
|
||||
assertCombo(PLAYER_2_ID, 0);
|
||||
}
|
||||
|
||||
private void setTime(double time) => AddStep($"set time {time}", () =>
|
||||
@ -149,71 +146,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void assertCombo(int userId, int expectedCombo)
|
||||
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
|
||||
|
||||
private class TestSpectatorStreamingClient : SpectatorStreamingClient
|
||||
{
|
||||
private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, bool> userSentStateDictionary = new Dictionary<int, bool>();
|
||||
|
||||
public TestSpectatorStreamingClient()
|
||||
: base(new DevelopmentEndpointConfiguration())
|
||||
{
|
||||
}
|
||||
|
||||
public void StartPlay(int userId, int beatmapId)
|
||||
{
|
||||
userBeatmapDictionary[userId] = beatmapId;
|
||||
userSentStateDictionary[userId] = false;
|
||||
sendState(userId, beatmapId);
|
||||
}
|
||||
|
||||
public void EndPlay(int userId, int beatmapId)
|
||||
{
|
||||
((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState
|
||||
{
|
||||
BeatmapID = beatmapId,
|
||||
RulesetID = 0,
|
||||
});
|
||||
userSentStateDictionary[userId] = false;
|
||||
}
|
||||
|
||||
public void SendFrames(int userId, int index, int count)
|
||||
{
|
||||
var frames = new List<LegacyReplayFrame>();
|
||||
|
||||
for (int i = index; i < index + count; i++)
|
||||
{
|
||||
var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1;
|
||||
frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
|
||||
}
|
||||
|
||||
var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames);
|
||||
((ISpectatorClient)this).UserSentFrames(userId, bundle);
|
||||
if (!userSentStateDictionary[userId])
|
||||
sendState(userId, userBeatmapDictionary[userId]);
|
||||
}
|
||||
|
||||
public override void WatchUser(int userId)
|
||||
{
|
||||
if (userSentStateDictionary[userId])
|
||||
{
|
||||
// usually the server would do this.
|
||||
sendState(userId, userBeatmapDictionary[userId]);
|
||||
}
|
||||
|
||||
base.WatchUser(userId);
|
||||
}
|
||||
|
||||
private void sendState(int userId, int beatmapId)
|
||||
{
|
||||
((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState
|
||||
{
|
||||
BeatmapID = beatmapId,
|
||||
RulesetID = 0,
|
||||
});
|
||||
userSentStateDictionary[userId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestUserLookupCache : UserLookupCache
|
||||
{
|
||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
@ -0,0 +1,313 @@
|
||||
// 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 System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
|
||||
{
|
||||
[Cached(typeof(SpectatorStreamingClient))]
|
||||
private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient();
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
private MultiSpectatorScreen spectatorScreen;
|
||||
|
||||
private readonly List<int> playingUserIds = new List<int>();
|
||||
private readonly Dictionary<int, int> nextFrame = new Dictionary<int, int>();
|
||||
|
||||
private BeatmapSetInfo importedSet;
|
||||
private BeatmapInfo importedBeatmap;
|
||||
private int importedBeatmapId;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
|
||||
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
|
||||
importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset sent frames", () => nextFrame.Clear());
|
||||
|
||||
AddStep("add streaming client", () =>
|
||||
{
|
||||
Remove(streamingClient);
|
||||
Add(streamingClient);
|
||||
});
|
||||
|
||||
AddStep("finish previous gameplay", () =>
|
||||
{
|
||||
foreach (var id in playingUserIds)
|
||||
streamingClient.EndPlay(id, importedBeatmapId);
|
||||
playingUserIds.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDelayedStart()
|
||||
{
|
||||
AddStep("start players silently", () =>
|
||||
{
|
||||
Client.CurrentMatchPlayingUserIds.Add(PLAYER_1_ID);
|
||||
Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
|
||||
playingUserIds.Add(PLAYER_1_ID);
|
||||
playingUserIds.Add(PLAYER_2_ID);
|
||||
nextFrame[PLAYER_1_ID] = 0;
|
||||
nextFrame[PLAYER_2_ID] = 0;
|
||||
});
|
||||
|
||||
loadSpectateScreen(false);
|
||||
|
||||
AddWaitStep("wait a bit", 10);
|
||||
AddStep("load player first_player_id", () => streamingClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
|
||||
AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 1);
|
||||
|
||||
AddWaitStep("wait a bit", 10);
|
||||
AddStep("load player second_player_id", () => streamingClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
|
||||
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGeneral()
|
||||
{
|
||||
int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray();
|
||||
|
||||
start(userIds);
|
||||
loadSpectateScreen();
|
||||
|
||||
sendFrames(userIds, 1000);
|
||||
AddWaitStep("wait a bit", 20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayersMustStartSimultaneously()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
// Send frames for one player only, both should remain paused.
|
||||
sendFrames(PLAYER_1_ID, 20);
|
||||
checkPausedInstant(PLAYER_1_ID, true);
|
||||
checkPausedInstant(PLAYER_2_ID, true);
|
||||
|
||||
// Send frames for the other player, both should now start playing.
|
||||
sendFrames(PLAYER_2_ID, 20);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
checkPausedInstant(PLAYER_2_ID, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
// Send frames for one player only, both should remain paused.
|
||||
sendFrames(PLAYER_1_ID, 1000);
|
||||
checkPausedInstant(PLAYER_1_ID, true);
|
||||
checkPausedInstant(PLAYER_2_ID, true);
|
||||
|
||||
// Wait for the start delay seconds...
|
||||
AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
|
||||
|
||||
// Player 1 should start playing by itself, player 2 should remain paused.
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
checkPausedInstant(PLAYER_2_ID, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayersContinueWhileOthersBuffer()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
// Send initial frames for both players. A few more for player 1.
|
||||
sendFrames(PLAYER_1_ID, 20);
|
||||
sendFrames(PLAYER_2_ID, 10);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
checkPausedInstant(PLAYER_2_ID, false);
|
||||
|
||||
// Eventually player 2 will pause, player 1 must remain running.
|
||||
checkPaused(PLAYER_2_ID, true);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
|
||||
// Eventually both players will run out of frames and should pause.
|
||||
checkPaused(PLAYER_1_ID, true);
|
||||
checkPausedInstant(PLAYER_2_ID, true);
|
||||
|
||||
// Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused.
|
||||
sendFrames(PLAYER_1_ID, 20);
|
||||
checkPausedInstant(PLAYER_2_ID, true);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
|
||||
// Send more frames for the second player. Both should be playing
|
||||
sendFrames(PLAYER_2_ID, 20);
|
||||
checkPausedInstant(PLAYER_2_ID, false);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayersCatchUpAfterFallingBehind()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
// Send initial frames for both players. A few more for player 1.
|
||||
sendFrames(PLAYER_1_ID, 1000);
|
||||
sendFrames(PLAYER_2_ID, 10);
|
||||
checkPausedInstant(PLAYER_1_ID, false);
|
||||
checkPausedInstant(PLAYER_2_ID, false);
|
||||
|
||||
// Eventually player 2 will run out of frames and should pause.
|
||||
checkPaused(PLAYER_2_ID, true);
|
||||
AddWaitStep("wait a few more frames", 10);
|
||||
|
||||
// Send more frames for player 2. It should unpause.
|
||||
sendFrames(PLAYER_2_ID, 1000);
|
||||
checkPausedInstant(PLAYER_2_ID, false);
|
||||
|
||||
// Player 2 should catch up to player 1 after unpausing.
|
||||
waitForCatchup(PLAYER_2_ID);
|
||||
AddWaitStep("wait a bit", 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMostInSyncUserIsAudioSource()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
sendFrames(PLAYER_1_ID, 10);
|
||||
sendFrames(PLAYER_2_ID, 20);
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
checkPaused(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, false);
|
||||
|
||||
sendFrames(PLAYER_1_ID, 100);
|
||||
waitForCatchup(PLAYER_1_ID);
|
||||
checkPaused(PLAYER_2_ID, true);
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
sendFrames(PLAYER_2_ID, 100);
|
||||
waitForCatchup(PLAYER_2_ID);
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
}
|
||||
|
||||
private void loadSpectateScreen(bool waitForPlayerLoad = true)
|
||||
{
|
||||
AddStep("load screen", () =>
|
||||
{
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
||||
Ruleset.Value = importedBeatmap.Ruleset;
|
||||
|
||||
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray()));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
||||
}
|
||||
|
||||
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
|
||||
|
||||
private void start(int[] userIds, int? beatmapId = null)
|
||||
{
|
||||
AddStep("start play", () =>
|
||||
{
|
||||
foreach (int id in userIds)
|
||||
{
|
||||
Client.CurrentMatchPlayingUserIds.Add(id);
|
||||
streamingClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
||||
playingUserIds.Add(id);
|
||||
nextFrame[id] = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void finish(int userId, int? beatmapId = null)
|
||||
{
|
||||
AddStep("end play", () =>
|
||||
{
|
||||
streamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId);
|
||||
playingUserIds.Remove(userId);
|
||||
nextFrame.Remove(userId);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
|
||||
|
||||
private void sendFrames(int[] userIds, int count = 10)
|
||||
{
|
||||
AddStep("send frames", () =>
|
||||
{
|
||||
foreach (int id in userIds)
|
||||
{
|
||||
streamingClient.SendFrames(id, nextFrame[id], count);
|
||||
nextFrame[id] += count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkPaused(int userId, bool state)
|
||||
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
|
||||
|
||||
private void checkPausedInstant(int userId, bool state)
|
||||
=> AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
|
||||
|
||||
private void assertMuted(int userId, bool muted)
|
||||
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
|
||||
|
||||
private void waitForCatchup(int userId)
|
||||
=> AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp);
|
||||
|
||||
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
|
||||
|
||||
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
|
||||
|
||||
internal class TestUserLookupCache : UserLookupCache
|
||||
{
|
||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(new User
|
||||
{
|
||||
Id = lookup,
|
||||
Username = $"User {lookup}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -11,7 +27,158 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
private TestMultiplayer multiplayerScreen;
|
||||
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
private TestMultiplayerClient client => multiplayerScreen.Client;
|
||||
private Room room => client.APIRoom;
|
||||
|
||||
public TestSceneMultiplayer()
|
||||
{
|
||||
loadMultiplayer();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestUserSetToIdleWhenBeatmapDeleted()
|
||||
{
|
||||
loadMultiplayer();
|
||||
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready));
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
|
||||
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
|
||||
{
|
||||
loadMultiplayer();
|
||||
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("join other user (ready, host)", () =>
|
||||
{
|
||||
client.AddUser(new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" });
|
||||
client.TransferHost(MultiplayerTestScene.PLAYER_1_ID);
|
||||
client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready);
|
||||
});
|
||||
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
|
||||
AddStep("click spectate button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("start match externally", () => client.StartMatch());
|
||||
|
||||
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
|
||||
{
|
||||
loadMultiplayer();
|
||||
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
|
||||
AddStep("join other user (ready, host)", () =>
|
||||
{
|
||||
client.AddUser(new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" });
|
||||
client.TransferHost(MultiplayerTestScene.PLAYER_1_ID);
|
||||
client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready);
|
||||
});
|
||||
|
||||
AddStep("click spectate button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("start match externally", () => client.StartMatch());
|
||||
|
||||
AddStep("restore beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
});
|
||||
|
||||
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
||||
}
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
AddStep("open room", () =>
|
||||
{
|
||||
multiplayerScreen.OpenNewRoom(room());
|
||||
});
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddWaitStep("wait for transition", 2);
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
}
|
||||
|
||||
private void loadMultiplayer()
|
||||
{
|
||||
AddStep("show", () =>
|
||||
{
|
||||
|
@ -6,14 +6,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays.Legacy;
|
||||
@ -22,6 +20,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Tests.Visual.Online;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -30,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private const int users = 16;
|
||||
|
||||
[Cached(typeof(SpectatorStreamingClient))]
|
||||
private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(users);
|
||||
private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming();
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
|
||||
@ -71,7 +70,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||
|
||||
streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
for (int i = 0; i < users; i++)
|
||||
streamingClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
|
||||
Client.CurrentMatchPlayingUserIds.Clear();
|
||||
Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers);
|
||||
@ -114,30 +114,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
|
||||
}
|
||||
|
||||
public class TestMultiplayerStreaming : SpectatorStreamingClient
|
||||
public class TestMultiplayerStreaming : TestSpectatorStreamingClient
|
||||
{
|
||||
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
||||
|
||||
private readonly int totalUsers;
|
||||
|
||||
public TestMultiplayerStreaming(int totalUsers)
|
||||
: base(new DevelopmentEndpointConfiguration())
|
||||
{
|
||||
this.totalUsers = totalUsers;
|
||||
}
|
||||
|
||||
public void Start(int beatmapId)
|
||||
{
|
||||
for (int i = 0; i < totalUsers; i++)
|
||||
{
|
||||
((ISpectatorClient)this).UserBeganPlaying(i, new SpectatorState
|
||||
{
|
||||
BeatmapID = beatmapId,
|
||||
RulesetID = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<int, FrameHeader> lastHeaders = new Dictionary<int, FrameHeader>();
|
||||
|
||||
public void RandomlyUpdateState()
|
||||
|
@ -119,8 +119,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("join other user (ready)", () =>
|
||||
{
|
||||
Client.AddUser(new User { Id = 55 });
|
||||
Client.ChangeUserState(55, MultiplayerUserState.Ready);
|
||||
Client.AddUser(new User { Id = PLAYER_1_ID });
|
||||
Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready);
|
||||
});
|
||||
|
||||
AddStep("click spectate button", () =>
|
||||
|
@ -120,9 +120,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEnabledWhenRoomOpen()
|
||||
[TestCase(MultiplayerRoomState.Open)]
|
||||
[TestCase(MultiplayerRoomState.WaitingForLoad)]
|
||||
[TestCase(MultiplayerRoomState.Playing)]
|
||||
public void TestEnabledWhenRoomOpenOrInGameplay(MultiplayerRoomState roomState)
|
||||
{
|
||||
AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState));
|
||||
assertSpectateButtonEnablement(true);
|
||||
}
|
||||
|
||||
@ -137,12 +140,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
[TestCase(MultiplayerRoomState.WaitingForLoad)]
|
||||
[TestCase(MultiplayerRoomState.Playing)]
|
||||
[TestCase(MultiplayerRoomState.Closed)]
|
||||
public void TestDisabledDuringGameplayOrClosed(MultiplayerRoomState roomState)
|
||||
public void TestDisabledWhenClosed(MultiplayerRoomState roomState)
|
||||
{
|
||||
AddStep($"change user to {roomState}", () => Client.ChangeRoomState(roomState));
|
||||
AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState));
|
||||
assertSpectateButtonEnablement(false);
|
||||
}
|
||||
|
||||
@ -156,8 +157,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestReadyButtonEnabledWhenHostAndUsersReady()
|
||||
{
|
||||
AddStep("add user", () => Client.AddUser(new User { Id = 55 }));
|
||||
AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready));
|
||||
AddStep("add user", () => Client.AddUser(new User { Id = PLAYER_1_ID }));
|
||||
AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready));
|
||||
|
||||
addClickSpectateButtonStep();
|
||||
assertReadyButtonEnablement(true);
|
||||
@ -168,11 +169,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
AddStep("add user and transfer host", () =>
|
||||
{
|
||||
Client.AddUser(new User { Id = 55 });
|
||||
Client.TransferHost(55);
|
||||
Client.AddUser(new User { Id = PLAYER_1_ID });
|
||||
Client.TransferHost(PLAYER_1_ID);
|
||||
});
|
||||
|
||||
AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready));
|
||||
AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready));
|
||||
|
||||
addClickSpectateButtonStep();
|
||||
assertReadyButtonEnablement(false);
|
||||
|
@ -12,7 +12,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Overlays.Dashboard;
|
||||
using osu.Game.Tests.Visual.Gameplay;
|
||||
using osu.Game.Tests.Visual.Spectator;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public class TestSceneCurrentlyPlayingDisplay : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(SpectatorStreamingClient))]
|
||||
private TestSceneSpectator.TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSceneSpectator.TestSpectatorStreamingClient();
|
||||
private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient();
|
||||
|
||||
private CurrentlyPlayingDisplay currentlyPlaying;
|
||||
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -45,14 +46,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
args.NewItems.Cast<Mod>().ForEach(mod => selectedMods.Add(new OsuSpriteText
|
||||
args.NewItems.AsNonNull().Cast<Mod>().ForEach(mod => selectedMods.Add(new OsuSpriteText
|
||||
{
|
||||
Text = mod.Acronym,
|
||||
}));
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
args.OldItems.Cast<Mod>().ForEach(mod =>
|
||||
args.OldItems.AsNonNull().Cast<Mod>().ForEach(mod =>
|
||||
{
|
||||
foreach (var selected in selectedMods)
|
||||
{
|
||||
|
@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -14,21 +17,34 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private NewsOverlay news;
|
||||
private NewsOverlay overlay;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() => Child = news = new NewsOverlay());
|
||||
public void SetUp() => Schedule(() => Child = overlay = new NewsOverlay());
|
||||
|
||||
[Test]
|
||||
public void TestRequest()
|
||||
{
|
||||
setUpNewsResponse(responseExample);
|
||||
AddStep("Show", () => news.Show());
|
||||
AddStep("Show article", () => news.ShowArticle("article"));
|
||||
AddStep("Show", () => overlay.Show());
|
||||
AddStep("Show article", () => overlay.ShowArticle("article"));
|
||||
}
|
||||
|
||||
private void setUpNewsResponse(GetNewsResponse r)
|
||||
=> AddStep("set up response", () =>
|
||||
[Test]
|
||||
public void TestCursorRequest()
|
||||
{
|
||||
setUpNewsResponse(responseWithCursor, "Set up cursor response");
|
||||
AddStep("Show", () => overlay.Show());
|
||||
AddUntilStep("Show More button is visible", () => showMoreButton?.Alpha == 1);
|
||||
setUpNewsResponse(responseWithNoCursor, "Set up no cursor response");
|
||||
AddStep("Click Show More", () => showMoreButton?.Click());
|
||||
AddUntilStep("Show More button is hidden", () => showMoreButton?.Alpha == 0);
|
||||
}
|
||||
|
||||
private ShowMoreButton showMoreButton => overlay.ChildrenOfType<ShowMoreButton>().FirstOrDefault();
|
||||
|
||||
private void setUpNewsResponse(GetNewsResponse r, string testName = "Set up response")
|
||||
=> AddStep(testName, () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
@ -40,7 +56,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
});
|
||||
|
||||
private GetNewsResponse responseExample => new GetNewsResponse
|
||||
private static GetNewsResponse responseExample => new GetNewsResponse
|
||||
{
|
||||
NewsPosts = new[]
|
||||
{
|
||||
@ -62,5 +78,37 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static GetNewsResponse responseWithCursor => new GetNewsResponse
|
||||
{
|
||||
NewsPosts = new[]
|
||||
{
|
||||
new APINewsPost
|
||||
{
|
||||
Title = "This post has an image which starts with \"/\" and has many authors!",
|
||||
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
Author = "someone, someone1, someone2, someone3, someone4",
|
||||
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
|
||||
PublishedAt = DateTimeOffset.Now
|
||||
}
|
||||
},
|
||||
Cursor = new Cursor()
|
||||
};
|
||||
|
||||
private static GetNewsResponse responseWithNoCursor => new GetNewsResponse
|
||||
{
|
||||
NewsPosts = new[]
|
||||
{
|
||||
new APINewsPost
|
||||
{
|
||||
Title = "This post has a full-url image! (HTML entity: &)",
|
||||
Preview = "boom (HTML entity: &)",
|
||||
Author = "user (HTML entity: &)",
|
||||
FirstImage = "https://assets.ppy.sh/artists/88/header.jpg",
|
||||
PublishedAt = DateTimeOffset.Now
|
||||
}
|
||||
},
|
||||
Cursor = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user