mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 16:12:57 +08:00
Merge branch 'single-bind-reset-button' of https://github.com/holaswords/osu into single-bind-reset-button
This commit is contained in:
commit
6a52622b3f
@ -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.507.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.513.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
@ -53,9 +53,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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -1,22 +1,18 @@
|
||||
// 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.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
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]
|
||||
@ -25,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create combo counters", () => SetContents(() => new SkinnableComboCounter()));
|
||||
AddStep("Create combo counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ComboCounter))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[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();
|
||||
@ -143,7 +146,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("create overlay", () =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(scoreProcessor, 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));
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,12 @@
|
||||
// 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
|
||||
@ -13,14 +16,20 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; }
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add editor overlay", () =>
|
||||
AddStep("reload skin editor", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE);
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -21,6 +19,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
@ -29,12 +30,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
SetContents(() =>
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var mods = new[] { ruleset.GetAutoplayMod() };
|
||||
var working = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
|
||||
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap);
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods);
|
||||
|
||||
var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty<Mod>())
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, mods)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[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.
|
||||
@ -76,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
SetContents(() =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(scoreProcessor, 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));
|
||||
|
@ -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,23 +1,18 @@
|
||||
// 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.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
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]
|
||||
@ -26,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Create score counters", () => SetContents(() => new SkinnableScoreCounter()));
|
||||
AddStep("Create score counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ScoreCounter))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -40,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestVeryLargeScore()
|
||||
{
|
||||
AddStep("set large score", () => scoreCounters.ForEach(counter => scoreProcessor.TotalScore.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
|
||||
|
@ -133,6 +133,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
@ -24,8 +26,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public class TestScenePlaylistsRoomSubScreen : RoomTestScene
|
||||
{
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
[Cached(typeof(IRoomManager))]
|
||||
private readonly TestRoomManager roomManager = new TestRoomManager();
|
||||
|
||||
@ -41,6 +41,18 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
|
||||
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case CreateRoomScoreRequest createRoomScoreRequest:
|
||||
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
@ -59,12 +71,16 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
Room.Name.Value = "my awesome room";
|
||||
Room.Host.Value = new User { Id = 2, Username = "peppy" };
|
||||
Room.RecentParticipants.Add(Room.Host.Value);
|
||||
Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
|
||||
Room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("start match", () => match.ChildrenOfType<PlaylistsReadyButton>().First().Click());
|
||||
AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -304,6 +304,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep(@"Sort by BPM", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM));
|
||||
AddStep(@"Sort by Length", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length));
|
||||
AddStep(@"Sort by Difficulty", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty));
|
||||
AddStep(@"Sort by Source", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Source));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,240 @@
|
||||
// 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.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneOsuMarkdownContainer : OsuTestScene
|
||||
{
|
||||
private OsuMarkdownContainer markdownContainer;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = overlayColour.Background5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(20),
|
||||
Child = markdownContainer = new OsuMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmphases()
|
||||
{
|
||||
AddStep("Emphases", () =>
|
||||
{
|
||||
markdownContainer.Text = @"_italic with underscore_
|
||||
*italic with asterisk*
|
||||
__bold with underscore__
|
||||
**bold with asterisk**
|
||||
*__italic with asterisk, bold with underscore__*
|
||||
_**italic with underscore, bold with asterisk**_";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHeading()
|
||||
{
|
||||
AddStep("Add Heading", () =>
|
||||
{
|
||||
markdownContainer.Text = @"# Header 1
|
||||
## Header 2
|
||||
### Header 3
|
||||
#### Header 4
|
||||
##### Header 5";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLink()
|
||||
{
|
||||
AddStep("Add Link", () =>
|
||||
{
|
||||
markdownContainer.Text = "[Welcome to osu!](https://osu.ppy.sh)";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkWithInlineText()
|
||||
{
|
||||
AddStep("Add Link with inline text", () =>
|
||||
{
|
||||
markdownContainer.Text = "Hey, [welcome to osu!](https://osu.ppy.sh) Please enjoy the game.";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInlineCode()
|
||||
{
|
||||
AddStep("Add inline code", () =>
|
||||
{
|
||||
markdownContainer.Text = "This is `inline code` text";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParagraph()
|
||||
{
|
||||
AddStep("Add paragraph", () =>
|
||||
{
|
||||
markdownContainer.Text = @"first paragraph
|
||||
|
||||
second paragraph
|
||||
|
||||
third paragraph";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFencedCodeBlock()
|
||||
{
|
||||
AddStep("Add Code Block", () =>
|
||||
{
|
||||
markdownContainer.Text = @"```markdown
|
||||
# Markdown code block
|
||||
|
||||
This is markdown code block.
|
||||
```";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSeparator()
|
||||
{
|
||||
AddStep("Add Seperator", () =>
|
||||
{
|
||||
markdownContainer.Text = @"Line above
|
||||
|
||||
---
|
||||
|
||||
Line below";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuote()
|
||||
{
|
||||
AddStep("Add quote", () =>
|
||||
{
|
||||
markdownContainer.Text =
|
||||
@"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTable()
|
||||
{
|
||||
AddStep("Add Table", () =>
|
||||
{
|
||||
markdownContainer.Text =
|
||||
@"| Left Aligned | Center Aligned | Right Aligned |
|
||||
| :------------------- | :--------------------: | ---------------------:|
|
||||
| Long Align Left Text | Long Align Center Text | Long Align Right Text |
|
||||
| Align Left | Align Center | Align Right |
|
||||
| Left | Center | Right |";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUnorderedList()
|
||||
{
|
||||
AddStep("Add Unordered List", () =>
|
||||
{
|
||||
markdownContainer.Text = @"- First item level 1
|
||||
- Second item level 1
|
||||
- First item level 2
|
||||
- First item level 3
|
||||
- Second item level 3
|
||||
- Third item level 3
|
||||
- First item level 4
|
||||
- Second item level 4
|
||||
- Third item level 4
|
||||
- Second item level 2
|
||||
- Third item level 2
|
||||
- Third item level 1";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOrderedList()
|
||||
{
|
||||
AddStep("Add Ordered List", () =>
|
||||
{
|
||||
markdownContainer.Text = @"1. First item level 1
|
||||
2. Second item level 1
|
||||
1. First item level 2
|
||||
1. First item level 3
|
||||
2. Second item level 3
|
||||
3. Third item level 3
|
||||
1. First item level 4
|
||||
2. Second item level 4
|
||||
3. Third item level 4
|
||||
2. Second item level 2
|
||||
3. Third item level 2
|
||||
3. Third item level 1";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLongMixedList()
|
||||
{
|
||||
AddStep("Add long mixed list", () =>
|
||||
{
|
||||
markdownContainer.Text = @"1. The osu! World Cup is a country-based team tournament played on the osu! game mode.
|
||||
- While this competition is planned as a 4 versus 4 setup, this may change depending on the number of incoming registrations.
|
||||
2. Beatmap scoring is based on Score V2.
|
||||
3. The beatmaps for each round will be announced by the map selectors in advance on the Sunday before the actual matches take place. Only these beatmaps will be used during the respective matches.
|
||||
- One beatmap will be a tiebreaker beatmap. This beatmap will only be played in case of a tie. **The only exception to this is the Qualifiers pool.**
|
||||
4. The match schedule will be settled by the Tournament Management (see the [scheduling instructions](#scheduling-instructions)).
|
||||
5. If no staff or referee is available, the match will be postponed.
|
||||
6. Use of the Visual Settings to alter background dim or disable beatmap elements like storyboards and skins are allowed.
|
||||
7. If the beatmap ends in a draw, the map will be nullified and replayed.
|
||||
8. If a player disconnects, their scores will not be counted towards their team's total.
|
||||
- Disconnects within 30 seconds or 25% of the beatmap length (whichever happens first) after beatmap begin can be aborted and/or rematched. This is up to the referee's discretion.
|
||||
9. Beatmaps cannot be reused in the same match unless the map was nullified.
|
||||
10. If less than the minimum required players attend, the maximum time the match can be postponed is 10 minutes.
|
||||
11. Exchanging players during a match is allowed without limitations.
|
||||
- **If a map rematch is required, exchanging players is not allowed. With the referee's discretion, an exception can be made if the previous roster is unavailable to play.**
|
||||
12. Lag is not a valid reason to nullify a beatmap.
|
||||
13. All players are supposed to keep the match running fluently and without delays. Penalties can be issued to the players if they cause excessive match delays.
|
||||
14. If a player disconnects between maps and the team cannot provide a replacement, the match can be delayed 10 minutes at maximum.
|
||||
15. All players and referees must be treated with respect. Instructions of the referees and tournament Management are to be followed. Decisions labeled as final are not to be objected.
|
||||
16. Disrupting the match by foul play, insulting and provoking other players or referees, delaying the match or other deliberate inappropriate misbehavior is strictly prohibited.
|
||||
17. The multiplayer chatrooms are subject to the [osu! community rules](/wiki/Rules).
|
||||
- Breaking the chat rules will result in a silence. Silenced players can not participate in multiplayer matches and must be exchanged for the time being.
|
||||
18. **The seeding method will be revealed after all the teams have played their Qualifier rounds.**
|
||||
19. Unexpected incidents are handled by the tournament management. Referees may allow higher tolerance depending on the circumstances. This is up to their discretion.
|
||||
20. Penalties for violating the tournament rules may include:
|
||||
- Exclusion of specific players for one beatmap
|
||||
- Exclusion of specific players for an entire match
|
||||
- Declaring the match as Lost by Default
|
||||
- Disqualification from the entire tournament
|
||||
- Disqualification from the current and future official tournaments until appealed
|
||||
- Any modification of these rules will be announced.";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
||||
public bool SkinLoaded => skin.IsResultAvailable;
|
||||
public ISkin Skin => skin.Value;
|
||||
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin();
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
||||
private readonly RecyclableLazy<ISkin> skin;
|
||||
|
||||
public abstract Stream GetStream(string storagePath);
|
||||
|
@ -472,7 +472,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete new file.
|
||||
/// Delete an existing file.
|
||||
/// </summary>
|
||||
/// <param name="model">The item to operate on.</param>
|
||||
/// <param name="file">The existing file to be deleted.</param>
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
@ -43,5 +45,23 @@ namespace osu.Game.Extensions
|
||||
/// <returns>The delta vector in Parent's coordinates.</returns>
|
||||
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
|
||||
drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta);
|
||||
|
||||
public static SkinnableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableInfo(component);
|
||||
|
||||
public static void ApplySkinnableInfo(this Drawable component, SkinnableInfo info)
|
||||
{
|
||||
// todo: can probably make this better via deserialisation directly using a common interface.
|
||||
component.Position = info.Position;
|
||||
component.Rotation = info.Rotation;
|
||||
component.Scale = info.Scale;
|
||||
component.Anchor = info.Anchor;
|
||||
component.Origin = info.Origin;
|
||||
|
||||
if (component is Container container)
|
||||
{
|
||||
foreach (var child in info.Children)
|
||||
container.Add(child.CreateInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
osu.Game/Extensions/TypeExtensions.cs
Normal file
31
osu.Game/Extensions/TypeExtensions.cs
Normal file
@ -0,0 +1,31 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
{
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <paramref name="type"/>'s <see cref="Type.AssemblyQualifiedName"/>
|
||||
/// with the assembly version, culture and public key token values removed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is usually used in extensibility scenarios (i.e. for custom rulesets or skins)
|
||||
/// when a version-agnostic identifier associated with a C# class - potentially originating from
|
||||
/// an external assembly - is needed.
|
||||
/// Leaving only the type and assembly names in such a scenario allows to preserve compatibility
|
||||
/// across assembly versions.
|
||||
/// </remarks>
|
||||
internal static string GetInvariantInstantiationInfo(this Type type)
|
||||
{
|
||||
string assemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
if (assemblyQualifiedName == null)
|
||||
throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type));
|
||||
|
||||
return string.Join(',', assemblyQualifiedName.Split(',').Take(2));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
// 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 Markdig;
|
||||
using Markdig.Extensions.AutoIdentifiers;
|
||||
using Markdig.Extensions.Tables;
|
||||
using Markdig.Extensions.Yaml;
|
||||
using Markdig.Syntax;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownContainer : MarkdownContainer
|
||||
{
|
||||
public OsuMarkdownContainer()
|
||||
{
|
||||
LineSpacing = 21;
|
||||
}
|
||||
|
||||
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
||||
{
|
||||
switch (markdownObject)
|
||||
{
|
||||
case YamlFrontMatterBlock _:
|
||||
// Don't parse YAML Frontmatter
|
||||
break;
|
||||
|
||||
case ListItemBlock listItemBlock:
|
||||
var isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered;
|
||||
var childContainer = CreateListItem(listItemBlock, level, isOrdered);
|
||||
container.Add(childContainer);
|
||||
foreach (var single in listItemBlock)
|
||||
base.AddMarkdownComponent(single, childContainer.Content, level);
|
||||
break;
|
||||
|
||||
default:
|
||||
base.AddMarkdownComponent(markdownObject, container, level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override SpriteText CreateSpriteText() => new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
};
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer();
|
||||
|
||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
||||
|
||||
protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock);
|
||||
|
||||
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
|
||||
|
||||
protected override MarkdownQuoteBlock CreateQuoteBlock(QuoteBlock quoteBlock) => new OsuMarkdownQuoteBlock(quoteBlock);
|
||||
|
||||
protected override MarkdownTable CreateTable(Table table) => new OsuMarkdownTable(table);
|
||||
|
||||
protected override MarkdownList CreateList(ListBlock listBlock) => new MarkdownList
|
||||
{
|
||||
Padding = new MarginPadding(0)
|
||||
};
|
||||
|
||||
protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level, bool isOrdered)
|
||||
{
|
||||
if (isOrdered)
|
||||
return new OsuMarkdownOrderedListItem(listItemBlock.Order);
|
||||
|
||||
return new OsuMarkdownUnorderedListItem(level);
|
||||
}
|
||||
|
||||
protected override MarkdownPipeline CreateBuilder()
|
||||
=> new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub)
|
||||
.UseEmojiAndSmiley()
|
||||
.UseYamlFrontMatter()
|
||||
.UseAdvancedExtensions().Build();
|
||||
}
|
||||
}
|
@ -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 Markdig.Syntax;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
||||
{
|
||||
// TODO : change to monospace font for this component
|
||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
: base(fencedCodeBlock)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateBackground() => new CodeBlockBackground();
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new CodeBlockTextFlowContainer();
|
||||
|
||||
private class CodeBlockBackground : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Colour = colourProvider.Background6;
|
||||
}
|
||||
}
|
||||
|
||||
private class CodeBlockTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Light1;
|
||||
Margin = new MarginPadding(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs
Normal file
75
osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 Markdig.Syntax;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownHeading : MarkdownHeading
|
||||
{
|
||||
private readonly int level;
|
||||
|
||||
public OsuMarkdownHeading(HeadingBlock headingBlock)
|
||||
: base(headingBlock)
|
||||
{
|
||||
level = headingBlock.Level;
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new HeadingTextFlowContainer
|
||||
{
|
||||
Weight = GetFontWeightByLevel(level),
|
||||
};
|
||||
|
||||
protected override float GetFontSizeByLevel(int level)
|
||||
{
|
||||
// Reference for this font size
|
||||
// https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9
|
||||
// https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161
|
||||
const float base_font_size = 14;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
return 30 / base_font_size;
|
||||
|
||||
case 2:
|
||||
return 26 / base_font_size;
|
||||
|
||||
case 3:
|
||||
return 20 / base_font_size;
|
||||
|
||||
case 4:
|
||||
return 18 / base_font_size;
|
||||
|
||||
case 5:
|
||||
return 16 / base_font_size;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual FontWeight GetFontWeightByLevel(int level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
return FontWeight.SemiBold;
|
||||
|
||||
default:
|
||||
return FontWeight.Bold;
|
||||
}
|
||||
}
|
||||
|
||||
private class HeadingTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
public FontWeight Weight { get; set; }
|
||||
|
||||
protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight));
|
||||
}
|
||||
}
|
||||
}
|
48
osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
Normal file
48
osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownLinkText : MarkdownLinkText
|
||||
{
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; }
|
||||
|
||||
private SpriteText spriteText;
|
||||
|
||||
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
||||
: base(text, linkInline)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
spriteText.Colour = colourProvider.Light2;
|
||||
}
|
||||
|
||||
public override SpriteText CreateSpriteText()
|
||||
{
|
||||
return spriteText = base.CreateSpriteText();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
spriteText.Colour = colourProvider.Light1;
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
spriteText.Colour = colourProvider.Light2;
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
}
|
44
osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs
Normal file
44
osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public abstract class OsuMarkdownListItem : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private IMarkdownTextComponent parentTextComponent { get; set; }
|
||||
|
||||
public FillFlowContainer Content { get; private set; }
|
||||
|
||||
protected OsuMarkdownListItem()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
CreateMarker(),
|
||||
Content = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10, 10),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual SpriteText CreateMarker() => parentTextComponent.CreateSpriteText();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownOrderedListItem : OsuMarkdownListItem
|
||||
{
|
||||
private const float left_padding = 30;
|
||||
|
||||
private readonly int order;
|
||||
|
||||
public OsuMarkdownOrderedListItem(int order)
|
||||
{
|
||||
this.order = order;
|
||||
Padding = new MarginPadding { Left = left_padding };
|
||||
}
|
||||
|
||||
protected override SpriteText CreateMarker() => base.CreateMarker().With(t =>
|
||||
{
|
||||
t.X = -left_padding;
|
||||
t.Text = $"{order}.";
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// 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 Markdig.Syntax;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownQuoteBlock : MarkdownQuoteBlock
|
||||
{
|
||||
public OsuMarkdownQuoteBlock(QuoteBlock quoteBlock)
|
||||
: base(quoteBlock)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateBackground() => new QuoteBackground();
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow()
|
||||
{
|
||||
return base.CreateTextFlow().With(f => f.Margin = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Horizontal = 20,
|
||||
});
|
||||
}
|
||||
|
||||
private class QuoteBackground : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = 2;
|
||||
Colour = colourProvider.Content2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// 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.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownSeparator : MarkdownSeparator
|
||||
{
|
||||
protected override Drawable CreateSeparator() => new Separator();
|
||||
|
||||
private class Separator : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 1;
|
||||
Colour = colourProvider.Background3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs
Normal file
18
osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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 Markdig.Extensions.Tables;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownTable : MarkdownTable
|
||||
{
|
||||
public OsuMarkdownTable(Table table)
|
||||
: base(table)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MarkdownTableCell CreateTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading) => new OsuMarkdownTableCell(cell, definition, isHeading);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// 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 Markdig.Extensions.Tables;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownTableCell : MarkdownTableCell
|
||||
{
|
||||
private readonly bool isHeading;
|
||||
|
||||
public OsuMarkdownTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading)
|
||||
: base(cell, definition)
|
||||
{
|
||||
this.isHeading = isHeading;
|
||||
Masking = false;
|
||||
BorderThickness = 0;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(CreateBorder(isHeading));
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new TableCellTextFlowContainer
|
||||
{
|
||||
Weight = isHeading ? FontWeight.Bold : FontWeight.Regular,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
protected virtual Box CreateBorder(bool isHeading)
|
||||
{
|
||||
if (isHeading)
|
||||
return new TableHeadBorder();
|
||||
|
||||
return new TableBodyBorder();
|
||||
}
|
||||
|
||||
private class TableHeadBorder : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Background3;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 2;
|
||||
Anchor = Anchor.BottomLeft;
|
||||
Origin = Anchor.BottomLeft;
|
||||
}
|
||||
}
|
||||
|
||||
private class TableBodyBorder : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Background4;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private class TableCellTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
public FontWeight Weight { get; set; }
|
||||
|
||||
protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// 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 Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownTextFlowContainer : MarkdownTextFlowContainer
|
||||
{
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; }
|
||||
|
||||
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
||||
|
||||
// TODO : Add background (colour B6) and change font to monospace
|
||||
protected override void AddCodeInLine(CodeInline codeInline)
|
||||
=> AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; });
|
||||
|
||||
protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic)
|
||||
=> CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic));
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownUnorderedListItem : OsuMarkdownListItem
|
||||
{
|
||||
private const float left_padding = 20;
|
||||
|
||||
private readonly int level;
|
||||
|
||||
public OsuMarkdownUnorderedListItem(int level)
|
||||
{
|
||||
this.level = level;
|
||||
|
||||
Padding = new MarginPadding { Left = left_padding };
|
||||
}
|
||||
|
||||
protected override SpriteText CreateMarker() => base.CreateMarker().With(t =>
|
||||
{
|
||||
t.Text = GetTextMarker(level);
|
||||
t.Font = t.Font.With(size: t.Font.Size / 2);
|
||||
t.Origin = Anchor.Centre;
|
||||
t.X = -left_padding / 2;
|
||||
t.Y = t.Font.Size;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Get text marker based on <paramref name="level"/>
|
||||
/// </summary>
|
||||
/// <param name="level">The markdown level of current list item.</param>
|
||||
/// <returns>The marker string of this list item</returns>
|
||||
protected virtual string GetTextMarker(int level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
return "●";
|
||||
|
||||
case 2:
|
||||
return "○";
|
||||
|
||||
default:
|
||||
return "■";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs
Normal file
18
osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class DangerousTriangleButton : TriangleButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.PinkDark;
|
||||
Triangles.ColourDark = colours.PinkDarker;
|
||||
Triangles.ColourLight = colours.Pink;
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.ToggleSkinEditor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
||||
|
||||
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
||||
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
||||
|
@ -6,13 +6,14 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Track whether the end-user is in an idle state, based on their last interaction with the game.
|
||||
/// </summary>
|
||||
public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IHandleGlobalKeyboardInput
|
||||
public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHandleGlobalKeyboardInput
|
||||
{
|
||||
private readonly double timeToIdle;
|
||||
|
||||
@ -58,6 +59,10 @@ namespace osu.Game.Input
|
||||
|
||||
public void OnReleased(PlatformAction action) => updateLastInteractionTime();
|
||||
|
||||
public bool OnPressed(GlobalAction action) => updateLastInteractionTime();
|
||||
|
||||
public void OnReleased(GlobalAction action) => updateLastInteractionTime();
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
switch (e)
|
||||
|
508
osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs
generated
Normal file
508
osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs
generated
Normal file
@ -0,0 +1,508 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
[DbContext(typeof(OsuDbContext))]
|
||||
[Migration("20210511060743_AddSkinInstantiationInfo")]
|
||||
partial class AddSkinInstantiationInfo
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<float>("ApproachRate");
|
||||
|
||||
b.Property<float>("CircleSize");
|
||||
|
||||
b.Property<float>("DrainRate");
|
||||
|
||||
b.Property<float>("OverallDifficulty");
|
||||
|
||||
b.Property<double>("SliderMultiplier");
|
||||
|
||||
b.Property<double>("SliderTickRate");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapDifficulty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("AudioLeadIn");
|
||||
|
||||
b.Property<double>("BPM");
|
||||
|
||||
b.Property<int>("BaseDifficultyID");
|
||||
|
||||
b.Property<int>("BeatDivisor");
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<bool>("Countdown");
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
b.Property<int>("GridSize");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<bool>("Hidden");
|
||||
|
||||
b.Property<double>("Length");
|
||||
|
||||
b.Property<bool>("LetterboxInBreaks");
|
||||
|
||||
b.Property<string>("MD5Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapID");
|
||||
|
||||
b.Property<string>("Path");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
||||
b.Property<double>("StarDifficulty");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoredBookmarks");
|
||||
|
||||
b.Property<double>("TimelineZoom");
|
||||
|
||||
b.Property<string>("Version");
|
||||
|
||||
b.Property<bool>("WidescreenStoryboard");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BaseDifficultyID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("Hash");
|
||||
|
||||
b.HasIndex("MD5Hash");
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("BeatmapInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Artist");
|
||||
|
||||
b.Property<string>("ArtistUnicode");
|
||||
|
||||
b.Property<string>("AudioFile");
|
||||
|
||||
b.Property<string>("AuthorString")
|
||||
.HasColumnName("Author");
|
||||
|
||||
b.Property<string>("BackgroundFile");
|
||||
|
||||
b.Property<int>("PreviewTime");
|
||||
|
||||
b.Property<string>("Source");
|
||||
|
||||
b.Property<string>("Tags");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.ToTable("BeatmapSetFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset>("DateAdded");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapSetID");
|
||||
|
||||
b.Property<bool>("Protected");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapSetID")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BeatmapSetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnName("Key");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("SkinInfoID");
|
||||
|
||||
b.Property<string>("StringValue")
|
||||
.HasColumnName("Value");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("ReferenceCount");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ReferenceCount");
|
||||
|
||||
b.ToTable("FileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("IntAction")
|
||||
.HasColumnName("Action");
|
||||
|
||||
b.Property<string>("KeysString")
|
||||
.HasColumnName("Keys");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("IntAction");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("KeyBinding");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||
{
|
||||
b.Property<int?>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("ShortName");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Available");
|
||||
|
||||
b.HasIndex("ShortName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RulesetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int?>("ScoreInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("ScoreInfoID");
|
||||
|
||||
b.ToTable("ScoreFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("Accuracy")
|
||||
.HasColumnType("DECIMAL(1,4)");
|
||||
|
||||
b.Property<int>("BeatmapInfoID");
|
||||
|
||||
b.Property<int>("Combo");
|
||||
|
||||
b.Property<DateTimeOffset>("Date");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("MaxCombo");
|
||||
|
||||
b.Property<string>("ModsJson")
|
||||
.HasColumnName("Mods");
|
||||
|
||||
b.Property<long?>("OnlineScoreID");
|
||||
|
||||
b.Property<double?>("PP");
|
||||
|
||||
b.Property<int>("Rank");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<string>("StatisticsJson")
|
||||
.HasColumnName("Statistics");
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<int?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
.HasColumnName("User");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapInfoID");
|
||||
|
||||
b.HasIndex("OnlineScoreID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("ScoreInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("SkinInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.ToTable("SkinFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Creator");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SkinInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseDifficultyID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("MetadataID");
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("BeatmapSets")
|
||||
.HasForeignKey("MetadataID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Settings")
|
||||
.HasForeignKey("SkinInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Scoring.ScoreInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ScoreInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
|
||||
.WithMany("Scores")
|
||||
.HasForeignKey("BeatmapInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("SkinInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
public partial class AddSkinInstantiationInfo : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InstantiationInfo",
|
||||
table: "SkinInfo",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InstantiationInfo",
|
||||
table: "SkinInfo");
|
||||
}
|
||||
}
|
||||
}
|
@ -141,8 +141,6 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.Property<string>("VideoFile");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
@ -352,7 +350,7 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<long?>("UserID")
|
||||
b.Property<int?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
@ -402,6 +400,8 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
@ -96,9 +96,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (newState == MultiplayerUserState.Spectating)
|
||||
return Task.CompletedTask; // Not supported yet.
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState);
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,11 @@ namespace osu.Game.Online.Spectator
|
||||
if (!playingUsers.Contains(userId))
|
||||
playingUsers.Add(userId);
|
||||
|
||||
playingUserStates[userId] = state;
|
||||
// UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched.
|
||||
// This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29).
|
||||
// We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations.
|
||||
if (watchingUsers.Contains(userId))
|
||||
playingUserStates[userId] = state;
|
||||
}
|
||||
|
||||
OnUserBeganPlaying?.Invoke(userId, state);
|
||||
@ -230,7 +234,7 @@ namespace osu.Game.Online.Spectator
|
||||
connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
|
||||
}
|
||||
|
||||
public void StopWatchingUser(int userId)
|
||||
public virtual void StopWatchingUser(int userId)
|
||||
{
|
||||
lock (userLock)
|
||||
{
|
||||
|
@ -82,6 +82,21 @@ namespace osu.Game
|
||||
|
||||
private SkinEditorOverlay skinEditor;
|
||||
|
||||
private Container overlayContent;
|
||||
|
||||
private Container rightFloatingOverlayContent;
|
||||
|
||||
private Container leftFloatingOverlayContent;
|
||||
|
||||
private Container topMostOverlayContent;
|
||||
|
||||
private ScalingContainer screenContainer;
|
||||
|
||||
private Container screenOffsetContainer;
|
||||
|
||||
[Resolved]
|
||||
private FrameworkConfigManager frameworkConfig { get; set; }
|
||||
|
||||
[Cached]
|
||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||
|
||||
@ -597,28 +612,35 @@ namespace osu.Game
|
||||
ActionRequested = action => volume.Adjust(action),
|
||||
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
||||
},
|
||||
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
|
||||
screenOffsetContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
receptor = new BackButton.Receptor(),
|
||||
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
||||
BackButton = new BackButton(receptor)
|
||||
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = () =>
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
var currentScreen = ScreenStack.CurrentScreen as IOsuScreen;
|
||||
receptor = new BackButton.Receptor(),
|
||||
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
||||
BackButton = new BackButton(receptor)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = () =>
|
||||
{
|
||||
var currentScreen = ScreenStack.CurrentScreen as IOsuScreen;
|
||||
|
||||
if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton())
|
||||
ScreenStack.Exit();
|
||||
if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton())
|
||||
ScreenStack.Exit();
|
||||
}
|
||||
},
|
||||
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||
}
|
||||
},
|
||||
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||
}
|
||||
},
|
||||
overlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||
@ -768,7 +790,7 @@ namespace osu.Game
|
||||
if (notifications.State.Value == Visibility.Visible)
|
||||
offset -= Toolbar.HEIGHT / 2;
|
||||
|
||||
screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
|
||||
screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
|
||||
}
|
||||
|
||||
Settings.State.ValueChanged += _ => updateScreenOffset();
|
||||
@ -935,19 +957,6 @@ namespace osu.Game
|
||||
{
|
||||
}
|
||||
|
||||
private Container overlayContent;
|
||||
|
||||
private Container rightFloatingOverlayContent;
|
||||
|
||||
private Container leftFloatingOverlayContent;
|
||||
|
||||
private Container topMostOverlayContent;
|
||||
|
||||
[Resolved]
|
||||
private FrameworkConfigManager frameworkConfig { get; set; }
|
||||
|
||||
private ScalingContainer screenContainer;
|
||||
|
||||
protected override bool OnExiting()
|
||||
{
|
||||
if (ScreenStack.CurrentScreen is Loader)
|
||||
@ -966,7 +975,7 @@ namespace osu.Game
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
screenContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||
screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
|
||||
|
||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||
|
@ -384,22 +384,13 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
public class ClearButton : TriangleButton
|
||||
public class ClearButton : DangerousTriangleButton
|
||||
{
|
||||
public ClearButton()
|
||||
{
|
||||
Text = "Clear";
|
||||
Size = new Vector2(80, 20);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Pink;
|
||||
|
||||
Triangles.ColourDark = colours.PinkDark;
|
||||
Triangles.ColourLight = colours.PinkLight;
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyButton : Container
|
||||
|
@ -49,10 +49,10 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
public class ResetButton : TriangleButton
|
||||
public class ResetButton : DangerousTriangleButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
Text = "Reset all bindings in section";
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -60,10 +60,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
Height = 20;
|
||||
|
||||
Content.CornerRadius = 5;
|
||||
|
||||
BackgroundColour = colours.PinkDark;
|
||||
Triangles.ColourDark = colours.PinkDarker;
|
||||
Triangles.ColourLight = colours.Pink;
|
||||
}
|
||||
}
|
||||
}
|
@ -100,7 +100,7 @@ namespace osu.Game.Overlays.News.Displays
|
||||
{
|
||||
content.Add(loaded);
|
||||
showMore.IsLoading = false;
|
||||
showMore.Show();
|
||||
showMore.Alpha = lastCursor == null ? 0 : 1;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
|
@ -69,13 +69,15 @@ namespace osu.Game.Overlays.Toolbar
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true);
|
||||
Current.BindValueChanged(_ => moveLineToCurrent(), true);
|
||||
Current.BindValueChanged(_ => moveLineToCurrent());
|
||||
|
||||
// Scheduled to allow the button flow layout to be computed before the line position is updated
|
||||
ScheduleAfterChildren(moveLineToCurrent);
|
||||
}
|
||||
|
||||
private bool hasInitialPosition;
|
||||
|
||||
// Scheduled to allow the flow layout to be computed before the line position is updated
|
||||
private void moveLineToCurrent() => ScheduleAfterChildren(() =>
|
||||
private void moveLineToCurrent()
|
||||
{
|
||||
if (SelectedTab != null)
|
||||
{
|
||||
@ -86,7 +88,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
hasInitialPosition = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;
|
||||
|
||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
displayVolume = value;
|
||||
|
||||
if (displayVolume > 0.99f)
|
||||
if (displayVolume >= 0.995f)
|
||||
{
|
||||
text.Text = "MAX";
|
||||
maxGlow.EffectColour = meterColour.Opacity(2f);
|
||||
|
@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||
/// <summary> An error occurred and a complete check could not be made. </summary>
|
||||
Error,
|
||||
|
||||
// TODO: Negligible issues should be hidden by default.
|
||||
/// <summary> A possible mistake so minor/unlikely that it can often be safely ignored. </summary>
|
||||
Negligible,
|
||||
}
|
||||
|
32
osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs
Normal file
32
osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs
Normal file
@ -0,0 +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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
public class ScrollingToolboxGroup : ToolboxGroup
|
||||
{
|
||||
protected readonly OsuScrollContainer Scroll;
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
public ScrollingToolboxGroup(string title, float scrollAreaHeight)
|
||||
: base(title)
|
||||
{
|
||||
base.Content.Add(Scroll = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = scrollAreaHeight,
|
||||
Child = Content = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +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 JetBrains.Annotations;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
public abstract class AutoGenerator : IAutoGenerator
|
||||
public abstract class AutoGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the auto replay and returns it.
|
||||
/// Every subclass of OsuAutoGeneratorBase should implement this!
|
||||
/// The default duration of a key press in milliseconds.
|
||||
/// </summary>
|
||||
public abstract Replay Generate();
|
||||
|
||||
#region Parameters
|
||||
public const double KEY_UP_DELAY = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap we're making.
|
||||
/// The beatmap the autoplay is generated for.
|
||||
/// </summary>
|
||||
protected IBeatmap Beatmap;
|
||||
|
||||
#endregion
|
||||
protected IBeatmap Beatmap { get; }
|
||||
|
||||
protected AutoGenerator(IBeatmap beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
||||
#region Constants
|
||||
|
||||
// Shared amongst all modes
|
||||
public const double KEY_UP_DELAY = 50;
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Generate the replay of the autoplay.
|
||||
/// </summary>
|
||||
public abstract Replay Generate();
|
||||
|
||||
protected virtual HitObject GetNextObject(int currentIndex)
|
||||
{
|
||||
@ -44,4 +40,37 @@ namespace osu.Game.Rulesets.Replays
|
||||
return Beatmap.HitObjects[currentIndex + 1];
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AutoGenerator<TFrame> : AutoGenerator
|
||||
where TFrame : ReplayFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// The replay frames of the autoplay.
|
||||
/// </summary>
|
||||
protected readonly List<TFrame> Frames = new List<TFrame>();
|
||||
|
||||
[CanBeNull]
|
||||
protected TFrame LastFrame => Frames.Count == 0 ? null : Frames[^1];
|
||||
|
||||
protected AutoGenerator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
public sealed override Replay Generate()
|
||||
{
|
||||
Frames.Clear();
|
||||
GenerateFrames();
|
||||
|
||||
return new Replay
|
||||
{
|
||||
Frames = Frames.OrderBy(frame => frame.Time).Cast<ReplayFrame>().ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the replay frames of the autoplay and populate <see cref="Frames"/>.
|
||||
/// </summary>
|
||||
protected abstract void GenerateFrames();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
// 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.Game.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
public interface IAutoGenerator
|
||||
{
|
||||
Replay Generate();
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Filter;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
@ -135,7 +136,7 @@ namespace osu.Game.Rulesets
|
||||
Name = Description,
|
||||
ShortName = ShortName,
|
||||
ID = (this as ILegacyRuleset)?.LegacyID,
|
||||
InstantiationInfo = GetType().AssemblyQualifiedName,
|
||||
InstantiationInfo = GetType().GetInvariantInstantiationInfo(),
|
||||
Available = true,
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Testing;
|
||||
|
||||
@ -18,20 +17,7 @@ namespace osu.Game.Rulesets
|
||||
|
||||
public string ShortName { get; set; }
|
||||
|
||||
private string instantiationInfo;
|
||||
|
||||
public string InstantiationInfo
|
||||
{
|
||||
get => instantiationInfo;
|
||||
set => instantiationInfo = abbreviateInstantiationInfo(value);
|
||||
}
|
||||
|
||||
private string abbreviateInstantiationInfo(string value)
|
||||
{
|
||||
// exclude version onwards, matching only on namespace and type.
|
||||
// this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old.
|
||||
return string.Join(',', value.Split(',').Take(2));
|
||||
}
|
||||
public string InstantiationInfo { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Available { get; set; }
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
@ -24,6 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// Includes selection and manipulation support via a <see cref="Components.SelectionHandler{T}"/>.
|
||||
/// </summary>
|
||||
public abstract class BlueprintContainer<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||
where T : class
|
||||
{
|
||||
protected DragBox DragBox { get; private set; }
|
||||
|
||||
@ -39,6 +42,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
protected readonly BindableList<T> SelectedItems = new BindableList<T>();
|
||||
|
||||
protected BlueprintContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -47,6 +52,24 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SelectedItems.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SelectionHandler = CreateSelectionHandler();
|
||||
SelectionHandler.DeselectAll = deselectAll;
|
||||
|
||||
|
@ -2,10 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -24,8 +22,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected readonly HitObjectComposer Composer;
|
||||
|
||||
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||
|
||||
protected EditorBlueprintContainer(HitObjectComposer composer)
|
||||
{
|
||||
Composer = composer;
|
||||
@ -34,23 +30,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
|
||||
selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var o in args.NewItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select();
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var o in args.OldItems)
|
||||
SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
SelectedItems.BindTo(Beatmap.SelectedHitObjects);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
private Container dragHandles;
|
||||
private SelectionBoxDragHandleContainer dragHandles;
|
||||
private FillFlowContainer buttons;
|
||||
|
||||
private OsuSpriteText selectionDetailsText;
|
||||
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
},
|
||||
}
|
||||
},
|
||||
dragHandles = new Container
|
||||
dragHandles = new SelectionBoxDragHandleContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// ensures that the centres of all drag handles line up with the middle of the selection box border.
|
||||
@ -220,75 +220,76 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private void addRotationComponents()
|
||||
{
|
||||
const float separation = 40;
|
||||
|
||||
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
|
||||
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Depth = float.MaxValue,
|
||||
Colour = colours.YellowLight,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0.3f,
|
||||
Size = new Vector2(BORDER_RADIUS, separation),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate")
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Y = -separation,
|
||||
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded
|
||||
}
|
||||
});
|
||||
addRotateHandle(Anchor.TopLeft);
|
||||
addRotateHandle(Anchor.TopRight);
|
||||
addRotateHandle(Anchor.BottomLeft);
|
||||
addRotateHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addYScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
|
||||
|
||||
addDragHandle(Anchor.TopCentre);
|
||||
addDragHandle(Anchor.BottomCentre);
|
||||
addScaleHandle(Anchor.TopCentre);
|
||||
addScaleHandle(Anchor.BottomCentre);
|
||||
}
|
||||
|
||||
private void addFullScaleComponents()
|
||||
{
|
||||
addDragHandle(Anchor.TopLeft);
|
||||
addDragHandle(Anchor.TopRight);
|
||||
addDragHandle(Anchor.BottomLeft);
|
||||
addDragHandle(Anchor.BottomRight);
|
||||
addScaleHandle(Anchor.TopLeft);
|
||||
addScaleHandle(Anchor.TopRight);
|
||||
addScaleHandle(Anchor.BottomLeft);
|
||||
addScaleHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addXScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
|
||||
|
||||
addDragHandle(Anchor.CentreLeft);
|
||||
addDragHandle(Anchor.CentreRight);
|
||||
addScaleHandle(Anchor.CentreLeft);
|
||||
addScaleHandle(Anchor.CentreRight);
|
||||
}
|
||||
|
||||
private void addButton(IconUsage icon, string tooltip, Action action)
|
||||
{
|
||||
buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip)
|
||||
var button = new SelectionBoxButton(icon, tooltip)
|
||||
{
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded,
|
||||
Action = action
|
||||
});
|
||||
};
|
||||
|
||||
button.OperationStarted += operationStarted;
|
||||
button.OperationEnded += operationEnded;
|
||||
buttons.Add(button);
|
||||
}
|
||||
|
||||
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle
|
||||
private void addScaleHandle(Anchor anchor)
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded
|
||||
});
|
||||
var handle = new SelectionBoxScaleHandle
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor)
|
||||
};
|
||||
|
||||
handle.OperationStarted += operationStarted;
|
||||
handle.OperationEnded += operationEnded;
|
||||
dragHandles.AddScaleHandle(handle);
|
||||
}
|
||||
|
||||
private void addRotateHandle(Anchor anchor)
|
||||
{
|
||||
var handle = new SelectionBoxRotationHandle
|
||||
{
|
||||
Anchor = anchor,
|
||||
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e))
|
||||
};
|
||||
|
||||
handle.OperationStarted += operationStarted;
|
||||
handle.OperationEnded += operationEnded;
|
||||
dragHandles.AddRotationHandle(handle);
|
||||
}
|
||||
|
||||
private int activeOperations;
|
||||
|
||||
|
@ -12,10 +12,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
|
||||
/// </summary>
|
||||
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
|
||||
public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
|
||||
{
|
||||
private SpriteIcon icon;
|
||||
|
||||
@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
public Action Action;
|
||||
|
||||
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip)
|
||||
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
||||
{
|
||||
this.iconUsage = iconUsage;
|
||||
|
||||
@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size *= 2;
|
||||
Size = new Vector2(20);
|
||||
AddInternal(icon = new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -49,16 +46,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
Action?.Invoke();
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateHoverState()
|
||||
{
|
||||
base.UpdateHoverState();
|
||||
icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black;
|
||||
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public string TooltipText { get; }
|
@ -0,0 +1,97 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base appearance for UI controls of the <see cref="SelectionBox"/>,
|
||||
/// such as scale handles, rotation handles, buttons, etc...
|
||||
/// </summary>
|
||||
public abstract class SelectionBoxControl : CompositeDrawable
|
||||
{
|
||||
public const double TRANSFORM_DURATION = 100;
|
||||
|
||||
public event Action OperationStarted;
|
||||
public event Action OperationEnded;
|
||||
|
||||
private Circle circle;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently holding the control with mouse.
|
||||
/// </summary>
|
||||
public bool IsHeld { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
protected OsuColour Colours { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
UpdateHoverState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
IsHeld = true;
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
IsHeld = false;
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected virtual void UpdateHoverState()
|
||||
{
|
||||
if (IsHeld)
|
||||
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
else
|
||||
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
|
||||
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected void TriggerOperationStarted() => OperationStarted?.Invoke();
|
||||
|
||||
protected void TriggerOperatoinEnded() => OperationEnded?.Invoke();
|
||||
}
|
||||
}
|
@ -2,75 +2,17 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxDragHandle : Container
|
||||
public abstract class SelectionBoxDragHandle : SelectionBoxControl
|
||||
{
|
||||
public Action OperationStarted;
|
||||
public Action OperationEnded;
|
||||
|
||||
public Action<DragEvent> HandleDrag { get; set; }
|
||||
|
||||
private Circle circle;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(10);
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateHoverState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
UpdateHoverState();
|
||||
}
|
||||
|
||||
protected bool HandlingMouse;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
HandlingMouse = true;
|
||||
UpdateHoverState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
HandlingMouse = false;
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
|
||||
UpdateHoverState();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
#region Internal events for SelectionBoxDragHandleContainer
|
||||
|
||||
internal event Action HoverGained;
|
||||
internal event Action HoverLost;
|
||||
internal event Action MouseDown;
|
||||
internal event Action MouseUp;
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
HandlingMouse = false;
|
||||
UpdateHoverState();
|
||||
base.OnMouseUp(e);
|
||||
bool result = base.OnHover(e);
|
||||
HoverGained?.Invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual void UpdateHoverState()
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark);
|
||||
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
HoverLost?.Invoke();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
bool result = base.OnMouseDown(e);
|
||||
MouseDown?.Invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
MouseUp?.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
|
||||
/// </summary>
|
||||
public class SelectionBoxDragHandleContainer : CompositeDrawable
|
||||
{
|
||||
private Container<SelectionBoxScaleHandle> scaleHandles;
|
||||
private Container<SelectionBoxRotationHandle> rotationHandles;
|
||||
|
||||
private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
scaleHandles = new Container<SelectionBoxScaleHandle>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
rotationHandles = new Container<SelectionBoxRotationHandle>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(-12.5f),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public void AddScaleHandle(SelectionBoxScaleHandle handle)
|
||||
{
|
||||
bindDragHandle(handle);
|
||||
scaleHandles.Add(handle);
|
||||
}
|
||||
|
||||
public void AddRotationHandle(SelectionBoxRotationHandle handle)
|
||||
{
|
||||
handle.Alpha = 0;
|
||||
handle.AlwaysPresent = true;
|
||||
|
||||
bindDragHandle(handle);
|
||||
rotationHandles.Add(handle);
|
||||
}
|
||||
|
||||
private void bindDragHandle(SelectionBoxDragHandle handle)
|
||||
{
|
||||
handle.HoverGained += updateRotationHandlesVisibility;
|
||||
handle.HoverLost += updateRotationHandlesVisibility;
|
||||
handle.MouseDown += updateRotationHandlesVisibility;
|
||||
handle.MouseUp += updateRotationHandlesVisibility;
|
||||
allDragHandles.Add(handle);
|
||||
}
|
||||
|
||||
private SelectionBoxRotationHandle displayedRotationHandle;
|
||||
private SelectionBoxDragHandle activeHandle;
|
||||
|
||||
private void updateRotationHandlesVisibility()
|
||||
{
|
||||
// if the active handle is a rotation handle and is held or hovered,
|
||||
// then no need to perform any updates to the rotation handles visibility.
|
||||
if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
|
||||
return;
|
||||
|
||||
displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
|
||||
displayedRotationHandle = null;
|
||||
|
||||
// if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
|
||||
if (activeHandle?.IsHeld == true)
|
||||
return;
|
||||
|
||||
activeHandle = rotationHandles.FirstOrDefault(h => h.IsHeld || h.IsHovered);
|
||||
activeHandle ??= allDragHandles.FirstOrDefault(h => h.IsHovered);
|
||||
|
||||
if (activeHandle != null)
|
||||
{
|
||||
displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
|
||||
displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation handle corresponding to the given handle.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
|
||||
{
|
||||
if (handle is SelectionBoxRotationHandle rotationHandle)
|
||||
return rotationHandle;
|
||||
|
||||
return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// 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.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxRotationHandle : SelectionBoxDragHandle
|
||||
{
|
||||
private SpriteIcon icon;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(15f);
|
||||
AddInternal(icon = new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.5f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Redo,
|
||||
Scale = new Vector2
|
||||
{
|
||||
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
|
||||
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateHoverState()
|
||||
{
|
||||
base.UpdateHoverState();
|
||||
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// 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 osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public class SelectionBoxScaleHandle : SelectionBoxDragHandle
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(10);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public class RoundedContentEditorScreen : EditorScreen
|
||||
public class EditorRoundedScreen : EditorScreen
|
||||
{
|
||||
public const int HORIZONTAL_PADDING = 100;
|
||||
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override Container<Drawable> Content => roundedContent;
|
||||
|
||||
public RoundedContentEditorScreen(EditorScreenMode mode)
|
||||
public EditorRoundedScreen(EditorScreenMode mode)
|
||||
: base(mode)
|
||||
{
|
||||
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
44
osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs
Normal file
44
osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public abstract class EditorRoundedScreenSettings : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = CreateSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract IReadOnlyList<Drawable> CreateSections();
|
||||
}
|
||||
}
|
61
osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs
Normal file
61
osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// 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.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public abstract class EditorRoundedScreenSettingsSection : CompositeDrawable
|
||||
{
|
||||
private const int header_height = 50;
|
||||
|
||||
protected abstract string HeaderText { get; }
|
||||
|
||||
protected FillFlowContainer Flow { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = HeaderText,
|
||||
Font = new FontUsage(size: 25, weight: "bold")
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Y = header_height,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = Flow = new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Spacing = new Vector2(10),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
public class SetupScreen : RoundedContentEditorScreen
|
||||
public class SetupScreen : EditorRoundedScreen
|
||||
{
|
||||
[Cached]
|
||||
private SectionsContainer<SetupSection> sections = new SectionsContainer<SetupSection>();
|
||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
public SetupScreenTabControl()
|
||||
{
|
||||
TabContainer.Margin = new MarginPadding { Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING };
|
||||
TabContainer.Margin = new MarginPadding { Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING };
|
||||
|
||||
AddInternal(background = new Box
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING
|
||||
Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING
|
||||
};
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
|
@ -2,44 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public class ControlPointSettings : CompositeDrawable
|
||||
public class ControlPointSettings : EditorRoundedScreenSettings
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = createSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||
protected override IReadOnlyList<Drawable> CreateSections() => new Drawable[]
|
||||
{
|
||||
new GroupSection(),
|
||||
new TimingSection(),
|
||||
|
@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public class TimingScreen : RoundedContentEditorScreen
|
||||
public class TimingScreen : EditorRoundedScreen
|
||||
{
|
||||
[Cached]
|
||||
private Bindable<ControlPointGroup> selectedGroup = new Bindable<ControlPointGroup>();
|
||||
|
27
osu.Game/Screens/Edit/Verify/InterpretationSection.cs
Normal file
27
osu.Game/Screens/Edit/Verify/InterpretationSection.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
internal class InterpretationSection : EditorRoundedScreenSettingsSection
|
||||
{
|
||||
protected override string HeaderText => "Interpretation";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(VerifyScreen verify)
|
||||
{
|
||||
Flow.Add(new SettingsEnumDropdown<DifficultyRating>
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
TooltipText = "Affects checks that depend on difficulty level",
|
||||
Current = verify.InterpretedDifficulty.GetBoundCopy()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
111
osu.Game/Screens/Edit/Verify/IssueList.cs
Normal file
111
osu.Game/Screens/Edit/Verify/IssueList.cs
Normal file
@ -0,0 +1,111 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
[Cached]
|
||||
public class IssueList : CompositeDrawable
|
||||
{
|
||||
private IssueTable table;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> workingBeatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private VerifyScreen verify { get; set; }
|
||||
|
||||
private IBeatmapVerifier rulesetVerifier;
|
||||
private BeatmapVerifier generalVerifier;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
generalVerifier = new BeatmapVerifier();
|
||||
rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background2,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = table = new IssueTable(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Text = "Refresh",
|
||||
Action = refresh,
|
||||
Size = new Vector2(120, 40),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
verify.InterpretedDifficulty.BindValueChanged(_ => refresh());
|
||||
verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh());
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
var issues = generalVerifier.Run(beatmap, workingBeatmap.Value);
|
||||
|
||||
if (rulesetVerifier != null)
|
||||
issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value));
|
||||
|
||||
issues = filter(issues);
|
||||
|
||||
table.Issues = issues
|
||||
.OrderBy(issue => issue.Template.Type)
|
||||
.ThenBy(issue => issue.Check.Metadata.Category);
|
||||
}
|
||||
|
||||
private IEnumerable<Issue> filter(IEnumerable<Issue> issues)
|
||||
{
|
||||
return issues.Where(issue => !verify.HiddenIssueTypes.Contains(issue.Template.Type));
|
||||
}
|
||||
}
|
||||
}
|
@ -2,45 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Verify
|
||||
{
|
||||
public class IssueSettings : CompositeDrawable
|
||||
public class IssueSettings : EditorRoundedScreenSettings
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Gray3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = createSections()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||
protected override IReadOnlyList<Drawable> CreateSections() => new Drawable[]
|
||||
{
|
||||
new InterpretationSection(),
|
||||
new VisibilitySection()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user