Merge branch 'master' into fix-spinner-rpm-user-rate-adjust
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.911.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.923.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 923 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
area.AttemptCatch(fruit);
|
area.AttemptCatch(fruit);
|
||||||
area.OnResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
||||||
|
|
||||||
drawable.Expire();
|
drawable.Expire();
|
||||||
});
|
});
|
||||||
|
65
osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneComboCounter : CatchSkinnableTestScene
|
||||||
|
{
|
||||||
|
private ScoreProcessor scoreProcessor;
|
||||||
|
|
||||||
|
private Color4 judgedObjectColour = Color4.White;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
SetContents(() => new CatchComboDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(2.5f),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatchComboCounter()
|
||||||
|
{
|
||||||
|
AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20);
|
||||||
|
AddStep("perform miss", () => performJudgement(HitResult.Miss));
|
||||||
|
|
||||||
|
AddStep("randomize judged object colour", () =>
|
||||||
|
{
|
||||||
|
judgedObjectColour = new Color4(
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performJudgement(HitResult type, Judgement judgement = null)
|
||||||
|
{
|
||||||
|
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
|
||||||
|
|
||||||
|
var result = new JudgementResult(judgedObject.HitObject, judgement ?? new Judgement()) { Type = type };
|
||||||
|
scoreProcessor.ApplyResult(result);
|
||||||
|
|
||||||
|
foreach (var counter in CreatedDrawables.Cast<CatchComboDisplay>())
|
||||||
|
counter.OnNewResult(judgedObject, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Droplet,
|
Droplet,
|
||||||
CatcherIdle,
|
CatcherIdle,
|
||||||
CatcherFail,
|
CatcherFail,
|
||||||
CatcherKiai
|
CatcherKiai,
|
||||||
|
CatchComboCounter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
{
|
{
|
||||||
@ -52,6 +53,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
case CatchSkinComponents.CatcherKiai:
|
case CatchSkinComponents.CatcherKiai:
|
||||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||||
|
|
||||||
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
|
var comboFont = GetConfig<LegacySetting, string>(LegacySetting.ComboPrefix)?.Value ?? "score";
|
||||||
|
|
||||||
|
// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
|
||||||
|
if (this.HasFont(comboFont))
|
||||||
|
return new LegacyComboCounter(Source);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
103
osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyComboCounter : CompositeDrawable, ICatchComboCounter
|
||||||
|
{
|
||||||
|
private readonly LegacyRollingCounter counter;
|
||||||
|
|
||||||
|
private readonly LegacyRollingCounter explosion;
|
||||||
|
|
||||||
|
public LegacyComboCounter(ISkin skin)
|
||||||
|
{
|
||||||
|
var fontName = skin.GetConfig<LegacySetting, string>(LegacySetting.ComboPrefix)?.Value ?? "score";
|
||||||
|
var fontOverlap = skin.GetConfig<LegacySetting, float>(LegacySetting.ComboOverlap)?.Value ?? -2f;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Alpha = 0f;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Scale = new Vector2(0.8f);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
explosion = new LegacyRollingCounter(skin, fontName, fontOverlap)
|
||||||
|
{
|
||||||
|
Alpha = 0.65f,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(1.5f),
|
||||||
|
},
|
||||||
|
counter = new LegacyRollingCounter(skin, fontName, fontOverlap)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastDisplayedCombo;
|
||||||
|
|
||||||
|
public void UpdateCombo(int combo, Color4? hitObjectColour = null)
|
||||||
|
{
|
||||||
|
if (combo == lastDisplayedCombo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// There may still be existing transforms to the counter (including value change after 250ms),
|
||||||
|
// finish them immediately before new transforms.
|
||||||
|
counter.SetCountWithoutRolling(lastDisplayedCombo);
|
||||||
|
|
||||||
|
lastDisplayedCombo = combo;
|
||||||
|
|
||||||
|
if (Time.Elapsed < 0)
|
||||||
|
{
|
||||||
|
// needs more work to make rewind somehow look good.
|
||||||
|
// basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle).
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo fell to zero, roll down and fade out the counter.
|
||||||
|
if (combo == 0)
|
||||||
|
{
|
||||||
|
counter.Current.Value = 0;
|
||||||
|
explosion.Current.Value = 0;
|
||||||
|
|
||||||
|
this.FadeOut(400, Easing.Out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeInFromZero().Then().Delay(1000).FadeOut(300);
|
||||||
|
|
||||||
|
counter.ScaleTo(1.5f)
|
||||||
|
.ScaleTo(0.8f, 250, Easing.Out)
|
||||||
|
.OnComplete(c => c.SetCountWithoutRolling(combo));
|
||||||
|
|
||||||
|
counter.Delay(250)
|
||||||
|
.ScaleTo(1f)
|
||||||
|
.ScaleTo(1.1f, 60).Then().ScaleTo(1f, 30);
|
||||||
|
|
||||||
|
explosion.Colour = hitObjectColour ?? Color4.White;
|
||||||
|
|
||||||
|
explosion.SetCountWithoutRolling(combo);
|
||||||
|
explosion.ScaleTo(1.5f)
|
||||||
|
.ScaleTo(1.9f, 400, Easing.Out)
|
||||||
|
.FadeOutFromOne(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a component that displays a skinned <see cref="ICatchComboCounter"/> and handles combo judgement results for updating it accordingly.
|
||||||
|
/// </summary>
|
||||||
|
public class CatchComboDisplay : SkinnableDrawable
|
||||||
|
{
|
||||||
|
private int currentCombo;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter;
|
||||||
|
|
||||||
|
public CatchComboDisplay()
|
||||||
|
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
ComboCounter?.UpdateCombo(currentCombo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
if (!result.Judgement.AffectsCombo || !result.HasResult)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (result.Type == HitResult.Miss)
|
||||||
|
{
|
||||||
|
updateCombo(0, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
if (!result.Judgement.AffectsCombo || !result.HasResult)
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCombo(int newCombo, Color4? hitObjectColour)
|
||||||
|
{
|
||||||
|
currentCombo = newCombo;
|
||||||
|
ComboCounter?.UpdateCombo(newCombo, hitObjectColour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
explodingFruitContainer,
|
explodingFruitContainer,
|
||||||
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
CatcherArea
|
CatcherArea,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public override void Add(DrawableHitObject h)
|
public override void Add(DrawableHitObject h)
|
||||||
{
|
{
|
||||||
h.OnNewResult += onNewResult;
|
h.OnNewResult += onNewResult;
|
||||||
|
h.OnRevertResult += onRevertResult;
|
||||||
|
|
||||||
base.Add(h);
|
base.Add(h);
|
||||||
|
|
||||||
@ -70,6 +71,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
=> CatcherArea.OnResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
|
|
||||||
|
private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
=> CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
||||||
|
|
||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public Container ExplodingFruitTarget
|
||||||
{
|
{
|
||||||
@ -34,10 +35,22 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
public CatcherArea(BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X };
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
comboDisplay = new CatchComboDisplay
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.None,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Margin = new MarginPadding { Bottom = 350f },
|
||||||
|
X = CatchPlayfield.CENTER_X
|
||||||
|
},
|
||||||
|
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
{
|
{
|
||||||
if (result.Judgement is IgnoreJudgement)
|
if (result.Judgement is IgnoreJudgement)
|
||||||
return;
|
return;
|
||||||
@ -86,8 +99,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
else
|
else
|
||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
comboDisplay.OnNewResult(fruit, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
|
=> comboDisplay.OnRevertResult(fruit, result);
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
public void OnReleased(CatchAction action)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -105,6 +123,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (state?.CatcherX != null)
|
if (state?.CatcherX != null)
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
MovableCatcher.X = state.CatcherX.Value;
|
||||||
|
|
||||||
|
comboDisplay.X = MovableCatcher.X;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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 osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface providing a set of methods to update the combo counter.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICatchComboCounter : IDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the counter to animate a transition from the old combo value it had to the current provided one.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is called regardless of whether the clock is rewinding.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="combo">The new combo value.</param>
|
||||||
|
/// <param name="hitObjectColour">The colour of the object if hit, null on miss.</param>
|
||||||
|
void UpdateCombo(int combo, Color4? hitObjectColour = null);
|
||||||
|
}
|
||||||
|
}
|
@ -34,11 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
|||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
ring = new RingPiece
|
ring = new RingPiece()
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,14 +46,24 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
distanceSnapToggle
|
distanceSnapToggle
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private BindableList<HitObject> selectedHitObjects;
|
||||||
|
|
||||||
|
private Bindable<HitObject> placementObject;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
|
LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
|
|
||||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
|
||||||
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
||||||
|
|
||||||
|
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||||
|
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
|
|
||||||
|
// we may be entering the screen with a selection already active
|
||||||
|
updateDistanceSnapGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||||
|
@ -46,7 +46,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
private void addConnection(FollowPointConnection connection)
|
private void addConnection(FollowPointConnection connection)
|
||||||
{
|
{
|
||||||
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
|
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
|
||||||
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
|
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) =>
|
||||||
|
{
|
||||||
|
int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value);
|
||||||
|
|
||||||
|
if (comp != 0)
|
||||||
|
return comp;
|
||||||
|
|
||||||
|
// we always want to insert the new item after equal ones.
|
||||||
|
// this is important for beatmaps with multiple hitobjects at the same point in time.
|
||||||
|
// if we use standard comparison insert order, there will be a churn of connections getting re-updated to
|
||||||
|
// the next object at the point-in-time, adding a construction/disposal overhead (see FollowPointConnection.End implementation's ClearInternal).
|
||||||
|
// this is easily visible on https://osu.ppy.sh/beatmapsets/150945#osu/372245
|
||||||
|
return -1;
|
||||||
|
}));
|
||||||
|
|
||||||
if (index < connections.Count - 1)
|
if (index < connections.Count - 1)
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class RingPiece : Container
|
public class RingPiece : CircularContainer
|
||||||
{
|
{
|
||||||
public RingPiece()
|
public RingPiece()
|
||||||
{
|
{
|
||||||
@ -18,21 +18,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChild = new CircularContainer
|
Masking = true;
|
||||||
|
BorderThickness = 10;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
{
|
{
|
||||||
Masking = true,
|
AlwaysPresent = true,
|
||||||
BorderThickness = 10,
|
Alpha = 0,
|
||||||
BorderColour = Color4.White,
|
RelativeSizeAxes = Axes.Both
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
var font = GetConfig<OsuSkinConfiguration, string>(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
|
var font = GetConfig<OsuSkinConfiguration, string>(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
|
||||||
var overlap = GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2;
|
var overlap = GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2;
|
||||||
|
|
||||||
return !hasFont(font)
|
return !this.HasFont(font)
|
||||||
? null
|
? null
|
||||||
: new LegacySpriteText(Source, font)
|
: new LegacySpriteText(Source, font)
|
||||||
{
|
{
|
||||||
@ -145,7 +145,5 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
|
|
||||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
return Source.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool hasFont(string fontName) => Source.GetTexture($"{fontName}-0") != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.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.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public class DrawableTestStrongHit : DrawableHit
|
||||||
|
{
|
||||||
|
private readonly HitResult type;
|
||||||
|
private readonly bool hitBoth;
|
||||||
|
|
||||||
|
public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true)
|
||||||
|
: base(new Hit
|
||||||
|
{
|
||||||
|
IsStrong = true,
|
||||||
|
StartTime = startTime,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// in order to create nested strong hit
|
||||||
|
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
this.hitBoth = hitBoth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadAsyncComplete()
|
||||||
|
{
|
||||||
|
base.LoadAsyncComplete();
|
||||||
|
|
||||||
|
Result.Type = type;
|
||||||
|
|
||||||
|
var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single();
|
||||||
|
nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(TaikoAction action) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -15,24 +14,29 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHitExplosion : TaikoSkinnableTestScene
|
public class TestSceneHitExplosion : TaikoSkinnableTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void TestNormalHit()
|
||||||
{
|
{
|
||||||
AddStep("Great", () => SetContents(() => getContentFor(HitResult.Great)));
|
AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great))));
|
||||||
AddStep("Good", () => SetContents(() => getContentFor(HitResult.Good)));
|
AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good))));
|
||||||
AddStep("Miss", () => SetContents(() => getContentFor(HitResult.Miss)));
|
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getContentFor(HitResult type)
|
[Test]
|
||||||
|
public void TestStrongHit([Values(false, true)] bool hitBoth)
|
||||||
{
|
{
|
||||||
DrawableTaikoHitObject hit;
|
AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth))));
|
||||||
|
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable getContentFor(DrawableTaikoHitObject hit)
|
||||||
|
{
|
||||||
return new Container
|
return new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
hit = createHit(type),
|
hit,
|
||||||
new HitExplosion(hit)
|
new HitExplosion(hit)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -43,5 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
|
private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
|
||||||
|
|
||||||
|
private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth)
|
||||||
|
=> new DrawableTestStrongHit(Time.Current, type, hitBoth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,9 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
private void addMissJudgement()
|
private void addMissJudgement()
|
||||||
{
|
{
|
||||||
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
DrawableTestHit h;
|
||||||
|
Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
|
||||||
|
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBarLine(bool major, double delay = scroll_time)
|
private void addBarLine(bool major, double delay = scroll_time)
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
yield return new TernaryStateMenuItem("Rim", action: state =>
|
yield return new TernaryStateMenuItem("Rim", action: state =>
|
||||||
{
|
{
|
||||||
|
ChangeHandler.BeginChange();
|
||||||
|
|
||||||
foreach (var h in hits)
|
foreach (var h in hits)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
@ -35,6 +37,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChangeHandler.EndChange();
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) }
|
State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) }
|
||||||
@ -47,6 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
yield return new TernaryStateMenuItem("Strong", action: state =>
|
yield return new TernaryStateMenuItem("Strong", action: state =>
|
||||||
{
|
{
|
||||||
|
ChangeHandler.BeginChange();
|
||||||
|
|
||||||
foreach (var h in hits)
|
foreach (var h in hits)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
EditorBeatmap?.UpdateHitObject(h);
|
EditorBeatmap?.UpdateHitObject(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChangeHandler.EndChange();
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
State = { Value = getTernaryState(hits, h => h.IsStrong) }
|
State = { Value = getTernaryState(hits, h => h.IsStrong) }
|
||||||
|
@ -75,7 +75,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.TaikoExplosionGood:
|
case TaikoSkinComponents.TaikoExplosionGood:
|
||||||
|
case TaikoSkinComponents.TaikoExplosionGoodStrong:
|
||||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||||
|
case TaikoSkinComponents.TaikoExplosionGreatStrong:
|
||||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||||
|
|
||||||
var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||||
@ -107,8 +109,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
case TaikoSkinComponents.TaikoExplosionGood:
|
case TaikoSkinComponents.TaikoExplosionGood:
|
||||||
return "taiko-hit100";
|
return "taiko-hit100";
|
||||||
|
|
||||||
|
case TaikoSkinComponents.TaikoExplosionGoodStrong:
|
||||||
|
return "taiko-hit100k";
|
||||||
|
|
||||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||||
return "taiko-hit300";
|
return "taiko-hit300";
|
||||||
|
|
||||||
|
case TaikoSkinComponents.TaikoExplosionGreatStrong:
|
||||||
|
return "taiko-hit300k";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type");
|
throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type");
|
||||||
|
@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
BarLine,
|
BarLine,
|
||||||
TaikoExplosionMiss,
|
TaikoExplosionMiss,
|
||||||
TaikoExplosionGood,
|
TaikoExplosionGood,
|
||||||
|
TaikoExplosionGoodStrong,
|
||||||
TaikoExplosionGreat,
|
TaikoExplosionGreat,
|
||||||
|
TaikoExplosionGreatStrong,
|
||||||
Scroller,
|
Scroller,
|
||||||
Mascot,
|
Mascot,
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.UI
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
@ -45,24 +47,41 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion());
|
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaikoSkinComponents getComponentName(HitResult resultType)
|
private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject)
|
||||||
{
|
{
|
||||||
|
var resultType = judgedObject.Result?.Type ?? HitResult.Great;
|
||||||
|
|
||||||
switch (resultType)
|
switch (resultType)
|
||||||
{
|
{
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
return TaikoSkinComponents.TaikoExplosionMiss;
|
return TaikoSkinComponents.TaikoExplosionMiss;
|
||||||
|
|
||||||
case HitResult.Good:
|
case HitResult.Good:
|
||||||
return TaikoSkinComponents.TaikoExplosionGood;
|
return useStrongExplosion(judgedObject)
|
||||||
|
? TaikoSkinComponents.TaikoExplosionGoodStrong
|
||||||
|
: TaikoSkinComponents.TaikoExplosionGood;
|
||||||
|
|
||||||
case HitResult.Great:
|
case HitResult.Great:
|
||||||
return TaikoSkinComponents.TaikoExplosionGreat;
|
return useStrongExplosion(judgedObject)
|
||||||
|
? TaikoSkinComponents.TaikoExplosionGreatStrong
|
||||||
|
: TaikoSkinComponents.TaikoExplosionGreat;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(resultType), "Invalid result type");
|
throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool useStrongExplosion(DrawableHitObject judgedObject)
|
||||||
|
{
|
||||||
|
if (!(judgedObject.HitObject is Hit))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return judgedObject.Result.Type == nestedHit.Result.Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -205,9 +205,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
X = result.IsHit ? judgedObject.Position.X : 0,
|
X = result.IsHit ? judgedObject.Position.X : 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.IsHit)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
|
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
|
||||||
|
|
||||||
addExplosion(judgedObject, type);
|
addExplosion(judgedObject, type);
|
||||||
@ -218,12 +215,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private void addDrumRollHit(DrawableDrumRollTick drawableTick) =>
|
private void addDrumRollHit(DrawableDrumRollTick drawableTick) =>
|
||||||
drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick));
|
drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick));
|
||||||
|
|
||||||
private void addExplosion(DrawableHitObject drawableObject, HitType type)
|
/// <remarks>
|
||||||
|
/// As legacy skins have different explosions for singular and double strong hits,
|
||||||
|
/// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame.
|
||||||
|
/// </remarks>
|
||||||
|
private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() =>
|
||||||
{
|
{
|
||||||
hitExplosionContainer.Add(new HitExplosion(drawableObject));
|
hitExplosionContainer.Add(new HitExplosion(drawableObject));
|
||||||
if (drawableObject.HitObject.Kiai)
|
if (drawableObject.HitObject.Kiai)
|
||||||
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
|
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
|
||||||
}
|
});
|
||||||
|
|
||||||
private class ProxyContainer : LifetimeManagementContainer
|
private class ProxyContainer : LifetimeManagementContainer
|
||||||
{
|
{
|
||||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
|
private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
|
||||||
{
|
{
|
||||||
public bool NewCombo { get; } = false;
|
public bool NewCombo { get; set; } = false;
|
||||||
public int ComboOffset { get; } = 0;
|
public int ComboOffset { get; } = 0;
|
||||||
|
|
||||||
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
||||||
|
134
osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Rulesets
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableRulesetDependencies : OsuTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDisposalDoesNotDisposeParentStores()
|
||||||
|
{
|
||||||
|
DrawableWithDependencies drawable = null;
|
||||||
|
TestTextureStore textureStore = null;
|
||||||
|
TestSampleStore sampleStore = null;
|
||||||
|
|
||||||
|
AddStep("add dependencies", () =>
|
||||||
|
{
|
||||||
|
Child = drawable = new DrawableWithDependencies();
|
||||||
|
textureStore = drawable.ParentTextureStore;
|
||||||
|
sampleStore = drawable.ParentSampleStore;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("clear children", Clear);
|
||||||
|
AddUntilStep("wait for disposal", () => drawable.IsDisposed);
|
||||||
|
|
||||||
|
AddStep("GC", () =>
|
||||||
|
{
|
||||||
|
drawable = null;
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed);
|
||||||
|
AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DrawableWithDependencies : CompositeDrawable
|
||||||
|
{
|
||||||
|
public TestTextureStore ParentTextureStore { get; private set; }
|
||||||
|
public TestSampleStore ParentSampleStore { get; private set; }
|
||||||
|
|
||||||
|
public DrawableWithDependencies()
|
||||||
|
{
|
||||||
|
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
|
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore());
|
||||||
|
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||||
|
|
||||||
|
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTextureStore : TextureStore
|
||||||
|
{
|
||||||
|
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSampleStore : ISampleStore
|
||||||
|
{
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SampleChannel Get(string name) => null;
|
||||||
|
|
||||||
|
public Task<SampleChannel> GetAsync(string name) => null;
|
||||||
|
|
||||||
|
public Stream GetStream(string name) => null;
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Volume => throw new NotImplementedException();
|
||||||
|
public BindableNumber<double> Balance => throw new NotImplementedException();
|
||||||
|
public BindableNumber<double> Frequency => throw new NotImplementedException();
|
||||||
|
public BindableNumber<double> Tempo => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateVolume => throw new NotImplementedException();
|
||||||
|
public IBindable<double> AggregateBalance => throw new NotImplementedException();
|
||||||
|
public IBindable<double> AggregateFrequency => throw new NotImplementedException();
|
||||||
|
public IBindable<double> AggregateTempo => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int PlaybackConcurrency { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -20,6 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
private GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||||
|
|
||||||
|
private TestSkinSourceContainer skinSource;
|
||||||
private SkinnableSound skinnableSound;
|
private SkinnableSound skinnableSound;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -29,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
skinSource = new TestSkinSourceContainer
|
||||||
{
|
{
|
||||||
Clock = gameplayClock,
|
Clock = gameplayClock,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -101,5 +107,55 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("sample not playing", () => !sample.Playing);
|
AddAssert("sample not playing", () => !sample.Playing);
|
||||||
AddAssert("sample not playing", () => !sample.Playing);
|
AddAssert("sample not playing", () => !sample.Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSkinChangeDoesntPlayOnPause()
|
||||||
|
{
|
||||||
|
DrawableSample sample = null;
|
||||||
|
AddStep("start sample", () =>
|
||||||
|
{
|
||||||
|
skinnableSound.Play();
|
||||||
|
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sample playing", () => sample.Playing);
|
||||||
|
|
||||||
|
AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true);
|
||||||
|
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
|
||||||
|
|
||||||
|
AddStep("trigger skin change", () => skinSource.TriggerSourceChanged());
|
||||||
|
|
||||||
|
AddAssert("retrieve and ensure current sample is different", () =>
|
||||||
|
{
|
||||||
|
DrawableSample oldSample = sample;
|
||||||
|
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
|
||||||
|
return sample != oldSample;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("new sample stopped", () => !sample.Playing);
|
||||||
|
AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false);
|
||||||
|
|
||||||
|
AddWaitStep("wait a bit", 5);
|
||||||
|
AddAssert("new sample not played", () => !sample.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cached(typeof(ISkinSource))]
|
||||||
|
private class TestSkinSourceContainer : Container, ISkinSource
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private ISkinSource source { get; set; }
|
||||||
|
|
||||||
|
public event Action SourceChanged;
|
||||||
|
|
||||||
|
public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component);
|
||||||
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
|
||||||
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
|
||||||
|
public void TriggerSourceChanged()
|
||||||
|
{
|
||||||
|
SourceChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public void TestMultipleLoads()
|
public void TestMultipleLoads()
|
||||||
{
|
{
|
||||||
var comments = exampleComments;
|
var comments = exampleComments;
|
||||||
int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel);
|
int topLevelCommentCount = exampleComments.Comments.Count;
|
||||||
|
|
||||||
AddStep("hide container", () => commentsContainer.Hide());
|
AddStep("hide container", () => commentsContainer.Hide());
|
||||||
setUpCommentsResponse(comments);
|
setUpCommentsResponse(comments);
|
||||||
|
@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
|
|
||||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||||
|
|
||||||
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Single().Enabled.Value);
|
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||||
|
|
||||||
AddStep("click contracted panel", () =>
|
AddStep("click contracted panel", () =>
|
||||||
{
|
{
|
||||||
@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Single().Enabled.Value);
|
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestResultsContainer : Container
|
private class TestResultsContainer : Container
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
handler = game;
|
handler = game;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<KeyBinding> DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings);
|
public override IEnumerable<KeyBinding> DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings);
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||||
{
|
{
|
||||||
@ -50,6 +50,14 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> EditorKeyBindings => new[]
|
||||||
|
{
|
||||||
|
new KeyBinding(new[] { InputKey.F1 }, GlobalAction.EditorComposeMode),
|
||||||
|
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode),
|
||||||
|
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||||
|
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||||
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
|
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
|
||||||
@ -68,7 +76,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume),
|
||||||
|
|
||||||
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
|
new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute),
|
||||||
|
|
||||||
new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev),
|
new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev),
|
||||||
new KeyBinding(InputKey.F1, GlobalAction.MusicPrev),
|
new KeyBinding(InputKey.F1, GlobalAction.MusicPrev),
|
||||||
@ -139,7 +147,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
[Description("Quick exit (Hold)")]
|
[Description("Quick exit (Hold)")]
|
||||||
QuickExit,
|
QuickExit,
|
||||||
|
|
||||||
// Game-wide beatmap msi ccotolle keybindings
|
// Game-wide beatmap music controller keybindings
|
||||||
[Description("Next track")]
|
[Description("Next track")]
|
||||||
MusicNext,
|
MusicNext,
|
||||||
|
|
||||||
@ -166,5 +174,18 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[Description("Pause")]
|
[Description("Pause")]
|
||||||
PauseGameplay,
|
PauseGameplay,
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
[Description("Setup Mode")]
|
||||||
|
EditorSetupMode,
|
||||||
|
|
||||||
|
[Description("Compose Mode")]
|
||||||
|
EditorComposeMode,
|
||||||
|
|
||||||
|
[Description("Design Mode")]
|
||||||
|
EditorDesignMode,
|
||||||
|
|
||||||
|
[Description("Timing Mode")]
|
||||||
|
EditorTimingMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
Add(new DefaultBindingsSubsection(manager));
|
Add(new DefaultBindingsSubsection(manager));
|
||||||
Add(new AudioControlKeyBindingsSubsection(manager));
|
Add(new AudioControlKeyBindingsSubsection(manager));
|
||||||
Add(new InGameKeyBindingsSubsection(manager));
|
Add(new InGameKeyBindingsSubsection(manager));
|
||||||
|
Add(new EditorKeyBindingsSubsection(manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultBindingsSubsection : KeyBindingsSubsection
|
private class DefaultBindingsSubsection : KeyBindingsSubsection
|
||||||
@ -56,5 +57,16 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
Defaults = manager.AudioControlKeyBindings;
|
Defaults = manager.AudioControlKeyBindings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class EditorKeyBindingsSubsection : KeyBindingsSubsection
|
||||||
|
{
|
||||||
|
protected override string Header => "Editor";
|
||||||
|
|
||||||
|
public EditorKeyBindingsSubsection(GlobalActionContainer manager)
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
Defaults = manager.EditorKeyBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
private RadioButtonCollection toolboxCollection;
|
private RadioButtonCollection toolboxCollection;
|
||||||
|
|
||||||
|
private ToolboxGroup togglesCollection;
|
||||||
|
|
||||||
protected HitObjectComposer(Ruleset ruleset)
|
protected HitObjectComposer(Ruleset ruleset)
|
||||||
{
|
{
|
||||||
Ruleset = ruleset;
|
Ruleset = ruleset;
|
||||||
@ -78,7 +80,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, "Could not load beatmap sucessfully!");
|
Logger.Error(e, "Could not load beatmap successfully!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +117,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } },
|
new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } },
|
||||||
new ToolboxGroup("toggles")
|
togglesCollection = new ToolboxGroup("toggles")
|
||||||
{
|
{
|
||||||
ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox
|
ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox
|
||||||
{
|
{
|
||||||
@ -190,9 +192,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
if (checkLeftToggleFromKey(e.Key, out var leftIndex))
|
||||||
{
|
{
|
||||||
var item = toolboxCollection.Items.ElementAtOrDefault(e.Key - Key.Number1);
|
var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex);
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
@ -201,9 +203,84 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkRightToggleFromKey(e.Key, out var rightIndex))
|
||||||
|
{
|
||||||
|
var item = togglesCollection.Children[rightIndex];
|
||||||
|
|
||||||
|
if (item is SettingsCheckbox checkbox)
|
||||||
|
{
|
||||||
|
checkbox.Bindable.Value = !checkbox.Bindable.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool checkLeftToggleFromKey(Key key, out int index)
|
||||||
|
{
|
||||||
|
if (key < Key.Number1 || key > Key.Number9)
|
||||||
|
{
|
||||||
|
index = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = key - Key.Number1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkRightToggleFromKey(Key key, out int index)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case Key.Q:
|
||||||
|
index = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.W:
|
||||||
|
index = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.E:
|
||||||
|
index = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.R:
|
||||||
|
index = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.T:
|
||||||
|
index = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.Y:
|
||||||
|
index = 5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.U:
|
||||||
|
index = 6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.I:
|
||||||
|
index = 7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.O:
|
||||||
|
index = 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.P:
|
||||||
|
index = 9;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
index = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs)
|
private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs)
|
||||||
{
|
{
|
||||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int ComboIndex { get; set; }
|
int ComboIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the HitObject starts a new combo.
|
||||||
|
/// </summary>
|
||||||
|
new bool NewCombo { get; set; }
|
||||||
|
|
||||||
Bindable<bool> LastInComboBindable { get; }
|
Bindable<bool> LastInComboBindable { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
@ -46,12 +47,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
|
TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
|
||||||
TextureStore.AddStore(parent.Get<TextureStore>());
|
CacheAs(TextureStore = new FallbackTextureStore(TextureStore, parent.Get<TextureStore>()));
|
||||||
Cache(TextureStore);
|
|
||||||
|
|
||||||
SampleStore = parent.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, @"Samples"));
|
SampleStore = parent.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, @"Samples"));
|
||||||
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||||
CacheAs<ISampleStore>(new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
|
CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
RulesetConfigManager = parent.Get<RulesetConfigCache>().GetConfigFor(ruleset);
|
RulesetConfigManager = parent.Get<RulesetConfigCache>().GetConfigFor(ruleset);
|
||||||
@ -82,69 +82,92 @@ namespace osu.Game.Rulesets.UI
|
|||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
|
|
||||||
SampleStore?.Dispose();
|
SampleStore?.Dispose();
|
||||||
|
TextureStore?.Dispose();
|
||||||
RulesetConfigManager = null;
|
RulesetConfigManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sample store which adds a fallback source.
|
/// A sample store which adds a fallback source and prevents disposal of the fallback source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
private class FallbackSampleStore : ISampleStore
|
||||||
/// This is a temporary implementation to workaround ISampleStore limitations.
|
|
||||||
/// </remarks>
|
|
||||||
public class FallbackSampleStore : ISampleStore
|
|
||||||
{
|
|
||||||
private readonly ISampleStore primary;
|
|
||||||
private readonly ISampleStore secondary;
|
|
||||||
|
|
||||||
public FallbackSampleStore(ISampleStore primary, ISampleStore secondary)
|
|
||||||
{
|
{
|
||||||
this.primary = primary;
|
private readonly ISampleStore primary;
|
||||||
this.secondary = secondary;
|
private readonly ISampleStore fallback;
|
||||||
|
|
||||||
|
public FallbackSampleStore(ISampleStore primary, ISampleStore fallback)
|
||||||
|
{
|
||||||
|
this.primary = primary;
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SampleChannel Get(string name) => primary.Get(name) ?? fallback.Get(name);
|
||||||
|
|
||||||
|
public Task<SampleChannel> GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name);
|
||||||
|
|
||||||
|
public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name);
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Volume => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Balance => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Frequency => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Tempo => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public IBindable<double> GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateVolume => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateBalance => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateFrequency => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateTempo => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public int PlaybackConcurrency
|
||||||
|
{
|
||||||
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
primary?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name);
|
/// <summary>
|
||||||
|
/// A texture store which adds a fallback source and prevents disposal of the fallback source.
|
||||||
public Task<SampleChannel> GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name);
|
/// </summary>
|
||||||
|
private class FallbackTextureStore : TextureStore
|
||||||
public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name);
|
|
||||||
|
|
||||||
public IEnumerable<string> GetAvailableResources() => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public BindableNumber<double> Volume => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public BindableNumber<double> Balance => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public BindableNumber<double> Frequency => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public BindableNumber<double> Tempo => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public IBindable<double> GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public IBindable<double> AggregateVolume => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public IBindable<double> AggregateBalance => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public IBindable<double> AggregateFrequency => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public IBindable<double> AggregateTempo => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public int PlaybackConcurrency
|
|
||||||
{
|
{
|
||||||
get => throw new NotSupportedException();
|
private readonly TextureStore primary;
|
||||||
set => throw new NotSupportedException();
|
private readonly TextureStore fallback;
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public FallbackTextureStore(TextureStore primary, TextureStore fallback)
|
||||||
{
|
{
|
||||||
|
this.primary = primary;
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
|
=> primary.Get(name, wrapModeS, wrapModeT) ?? fallback.Get(name, wrapModeS, wrapModeT);
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
primary?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,20 @@ namespace osu.Game.Rulesets.UI
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
unbindStartTimeMap();
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Clear(bool disposeChildren = true)
|
public virtual void Clear(bool disposeChildren = true)
|
||||||
{
|
{
|
||||||
ClearInternal(disposeChildren);
|
ClearInternal(disposeChildren);
|
||||||
|
unbindStartTimeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unbindStartTimeMap()
|
||||||
|
{
|
||||||
foreach (var kvp in startTimeMap)
|
foreach (var kvp in startTimeMap)
|
||||||
kvp.Value.bindable.UnbindAll();
|
kvp.Value.bindable.UnbindAll();
|
||||||
startTimeMap.Clear();
|
startTimeMap.Clear();
|
||||||
|
@ -37,7 +37,21 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All the <see cref="DrawableHitObject"/>s contained in this <see cref="Playfield"/> and all <see cref="NestedPlayfields"/>.
|
/// All the <see cref="DrawableHitObject"/>s contained in this <see cref="Playfield"/> and all <see cref="NestedPlayfields"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<DrawableHitObject> AllHitObjects => HitObjectContainer?.Objects.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects)) ?? Enumerable.Empty<DrawableHitObject>();
|
public IEnumerable<DrawableHitObject> AllHitObjects
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (HitObjectContainer == null)
|
||||||
|
return Enumerable.Empty<DrawableHitObject>();
|
||||||
|
|
||||||
|
var enumerable = HitObjectContainer.Objects;
|
||||||
|
|
||||||
|
if (nestedPlayfields.IsValueCreated)
|
||||||
|
enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects));
|
||||||
|
|
||||||
|
return enumerable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All <see cref="Playfield"/>s nested inside this <see cref="Playfield"/>.
|
/// All <see cref="Playfield"/>s nested inside this <see cref="Playfield"/>.
|
||||||
|
@ -22,10 +22,12 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
{
|
{
|
||||||
trackTimer = new OsuSpriteText
|
trackTimer = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomLeft,
|
Anchor = Anchor.CentreRight,
|
||||||
RelativePositionAxes = Axes.Y,
|
Origin = Anchor.CentreRight,
|
||||||
Font = OsuFont.GetFont(size: 22, fixedWidth: true),
|
// intentionally fudged centre to avoid movement of the number portion when
|
||||||
Y = 0.5f,
|
// going negative.
|
||||||
|
X = -35,
|
||||||
|
Font = OsuFont.GetFont(size: 25, fixedWidth: true),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -34,7 +36,8 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
trackTimer.Text = TimeSpan.FromMilliseconds(editorClock.CurrentTime).ToString(@"mm\:ss\:fff");
|
var timespan = TimeSpan.FromMilliseconds(editorClock.CurrentTime);
|
||||||
|
trackTimer.Text = $"{(timespan < TimeSpan.Zero ? "-" : string.Empty)}{timespan:mm\\:ss\\:fff}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected EditorBeatmap EditorBeatmap { get; private set; }
|
protected EditorBeatmap EditorBeatmap { get; private set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IEditorChangeHandler changeHandler { get; set; }
|
protected IEditorChangeHandler ChangeHandler { get; private set; }
|
||||||
|
|
||||||
public SelectionHandler()
|
public SelectionHandler()
|
||||||
{
|
{
|
||||||
@ -193,12 +194,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void deleteSelected()
|
private void deleteSelected()
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
ChangeHandler?.BeginChange();
|
||||||
|
|
||||||
foreach (var h in selectedBlueprints.ToList())
|
foreach (var h in selectedBlueprints.ToList())
|
||||||
EditorBeatmap?.Remove(h.HitObject);
|
EditorBeatmap?.Remove(h.HitObject);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
ChangeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -254,7 +255,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void AddHitSample(string sampleName)
|
public void AddHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
ChangeHandler?.BeginChange();
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects)
|
foreach (var h in SelectedHitObjects)
|
||||||
{
|
{
|
||||||
@ -265,7 +266,30 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
||||||
}
|
}
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
ChangeHandler?.EndChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the new combo state of all selected <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Whether to set or unset.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
|
||||||
|
public void SetNewCombo(bool state)
|
||||||
|
{
|
||||||
|
ChangeHandler?.BeginChange();
|
||||||
|
|
||||||
|
foreach (var h in SelectedHitObjects)
|
||||||
|
{
|
||||||
|
var comboInfo = h as IHasComboInformation;
|
||||||
|
|
||||||
|
if (comboInfo == null)
|
||||||
|
throw new InvalidOperationException($"Tried to change combo state of a {h.GetType()}, which doesn't implement {nameof(IHasComboInformation)}");
|
||||||
|
|
||||||
|
comboInfo.NewCombo = state;
|
||||||
|
EditorBeatmap?.UpdateHitObject(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -274,12 +298,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void RemoveHitSample(string sampleName)
|
public void RemoveHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
ChangeHandler?.BeginChange();
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects)
|
foreach (var h in SelectedHitObjects)
|
||||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
ChangeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -297,6 +321,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints));
|
items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints));
|
||||||
|
|
||||||
|
if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation))
|
||||||
|
items.Add(createNewComboMenuItem());
|
||||||
|
|
||||||
if (selectedBlueprints.Count == 1)
|
if (selectedBlueprints.Count == 1)
|
||||||
items.AddRange(selectedBlueprints[0].ContextMenuItems);
|
items.AddRange(selectedBlueprints[0].ContextMenuItems);
|
||||||
|
|
||||||
@ -326,6 +353,41 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected virtual IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
protected virtual IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
||||||
=> Enumerable.Empty<MenuItem>();
|
=> Enumerable.Empty<MenuItem>();
|
||||||
|
|
||||||
|
private MenuItem createNewComboMenuItem()
|
||||||
|
{
|
||||||
|
return new TernaryStateMenuItem("New combo", MenuItemType.Standard, setNewComboState)
|
||||||
|
{
|
||||||
|
State = { Value = getHitSampleState() }
|
||||||
|
};
|
||||||
|
|
||||||
|
void setNewComboState(TernaryState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TernaryState.False:
|
||||||
|
SetNewCombo(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.True:
|
||||||
|
SetNewCombo(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TernaryState getHitSampleState()
|
||||||
|
{
|
||||||
|
int countExisting = selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo);
|
||||||
|
|
||||||
|
if (countExisting == 0)
|
||||||
|
return TernaryState.False;
|
||||||
|
|
||||||
|
if (countExisting < SelectedHitObjects.Count())
|
||||||
|
return TernaryState.Indeterminate;
|
||||||
|
|
||||||
|
return TernaryState.True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MenuItem createHitSampleMenuItem(string name, string sampleName)
|
private MenuItem createHitSampleMenuItem(string name, string sampleName)
|
||||||
{
|
{
|
||||||
return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState)
|
return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState)
|
||||||
|
@ -79,6 +79,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
private EditorBeatmap editorBeatmap;
|
private EditorBeatmap editorBeatmap;
|
||||||
private EditorChangeHandler changeHandler;
|
private EditorChangeHandler changeHandler;
|
||||||
|
|
||||||
|
private EditorMenuBar menuBar;
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
|
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
|
||||||
@ -133,8 +135,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
updateLastSavedHash();
|
updateLastSavedHash();
|
||||||
|
|
||||||
EditorMenuBar menuBar;
|
|
||||||
|
|
||||||
OsuMenuItem undoMenuItem;
|
OsuMenuItem undoMenuItem;
|
||||||
OsuMenuItem redoMenuItem;
|
OsuMenuItem redoMenuItem;
|
||||||
|
|
||||||
@ -374,14 +374,32 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
if (action == GlobalAction.Back)
|
switch (action)
|
||||||
{
|
{
|
||||||
// as we don't want to display the back button, manual handling of exit action is required.
|
case GlobalAction.Back:
|
||||||
this.Exit();
|
// as we don't want to display the back button, manual handling of exit action is required.
|
||||||
return true;
|
this.Exit();
|
||||||
}
|
return true;
|
||||||
|
|
||||||
return false;
|
case GlobalAction.EditorComposeMode:
|
||||||
|
menuBar.Mode.Value = EditorScreenMode.Compose;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorDesignMode:
|
||||||
|
menuBar.Mode.Value = EditorScreenMode.Design;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorTimingMode:
|
||||||
|
menuBar.Mode.Value = EditorScreenMode.Timing;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorSetupMode:
|
||||||
|
menuBar.Mode.Value = EditorScreenMode.SongSetup;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnReleased(GlobalAction action)
|
public void OnReleased(GlobalAction action)
|
||||||
|
@ -94,6 +94,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadComponentAsync(CreateMainContent(), content =>
|
LoadComponentAsync(CreateMainContent(), content =>
|
||||||
{
|
{
|
||||||
spinner.State.Value = Visibility.Hidden;
|
spinner.State.Value = Visibility.Hidden;
|
||||||
|
@ -89,6 +89,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void applyToPlayfield(Playfield playfield)
|
private void applyToPlayfield(Playfield playfield)
|
||||||
{
|
{
|
||||||
|
double failTime = playfield.Time.Current;
|
||||||
|
|
||||||
foreach (var nested in playfield.NestedPlayfields)
|
foreach (var nested in playfield.NestedPlayfields)
|
||||||
applyToPlayfield(nested);
|
applyToPlayfield(nested);
|
||||||
|
|
||||||
@ -97,13 +99,29 @@ namespace osu.Game.Screens.Play
|
|||||||
if (appliedObjects.Contains(obj))
|
if (appliedObjects.Contains(obj))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
obj.RotateTo(RNG.NextSingle(-90, 90), duration);
|
float rotation = RNG.NextSingle(-90, 90);
|
||||||
obj.ScaleTo(obj.Scale * 0.5f, duration);
|
Vector2 originalPosition = obj.Position;
|
||||||
obj.MoveToOffset(new Vector2(0, 400), duration);
|
Vector2 originalScale = obj.Scale;
|
||||||
|
|
||||||
|
dropOffScreen(obj, failTime, rotation, originalScale, originalPosition);
|
||||||
|
|
||||||
|
// need to reapply the fail drop after judgement state changes
|
||||||
|
obj.ApplyCustomUpdateState += (o, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition);
|
||||||
|
|
||||||
appliedObjects.Add(obj);
|
appliedObjects.Add(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dropOffScreen(DrawableHitObject obj, double failTime, float randomRotation, Vector2 originalScale, Vector2 originalPosition)
|
||||||
|
{
|
||||||
|
using (obj.BeginAbsoluteSequence(failTime))
|
||||||
|
{
|
||||||
|
obj.RotateTo(randomRotation, duration);
|
||||||
|
obj.ScaleTo(originalScale * 0.5f, duration);
|
||||||
|
obj.MoveTo(originalPosition + new Vector2(0, 400), duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -399,7 +399,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, "Could not load beatmap sucessfully!");
|
Logger.Error(e, "Could not load beatmap successfully!");
|
||||||
//couldn't load, hard abort!
|
//couldn't load, hard abort!
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -273,10 +273,10 @@ namespace osu.Game.Screens.Ranking
|
|||||||
detachedPanelContainer.Add(expandedPanel);
|
detachedPanelContainer.Add(expandedPanel);
|
||||||
|
|
||||||
// Move into its original location in the local container first, then to the final location.
|
// Move into its original location in the local container first, then to the final location.
|
||||||
var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos);
|
var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos).X;
|
||||||
expandedPanel.MoveTo(origLocation)
|
expandedPanel.MoveToX(origLocation)
|
||||||
.Then()
|
.Then()
|
||||||
.MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint);
|
.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint);
|
||||||
|
|
||||||
// Hide contracted panels.
|
// Hide contracted panels.
|
||||||
foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted))
|
foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted))
|
||||||
|
@ -99,6 +99,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
{
|
{
|
||||||
var panel = new ScorePanel(score)
|
var panel = new ScorePanel(score)
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
PostExpandAction = () => PostExpandAction?.Invoke()
|
PostExpandAction = () => PostExpandAction?.Invoke()
|
||||||
}.With(p =>
|
}.With(p =>
|
||||||
{
|
{
|
||||||
|
@ -75,7 +75,23 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (newScore.HitEvents == null || newScore.HitEvents.Count == 0)
|
if (newScore.HitEvents == null || newScore.HitEvents.Count == 0)
|
||||||
content.Add(new MessagePlaceholder("Score has no statistics :("));
|
{
|
||||||
|
content.Add(new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new MessagePlaceholder("Extended statistics are only available after watching a replay!"),
|
||||||
|
new ReplayDownloadButton(newScore)
|
||||||
|
{
|
||||||
|
Scale = new Vector2(1.5f),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spinner.Show();
|
spinner.Show();
|
||||||
@ -91,7 +107,10 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
{
|
{
|
||||||
var rows = new FillFlowContainer
|
var rows = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(30, 15),
|
Spacing = new Vector2(30, 15),
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -13,25 +14,62 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BeatmapSkinProvidingContainer : SkinProvidingContainer
|
public class BeatmapSkinProvidingContainer : SkinProvidingContainer
|
||||||
{
|
{
|
||||||
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
private Bindable<bool> beatmapSkins;
|
||||||
private readonly Bindable<bool> beatmapHitsounds = new Bindable<bool>();
|
private Bindable<bool> beatmapHitsounds;
|
||||||
|
|
||||||
protected override bool AllowConfigurationLookup => beatmapSkins.Value;
|
protected override bool AllowConfigurationLookup
|
||||||
protected override bool AllowDrawableLookup(ISkinComponent component) => beatmapSkins.Value;
|
{
|
||||||
protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value;
|
get
|
||||||
protected override bool AllowSampleLookup(ISampleInfo componentName) => beatmapHitsounds.Value;
|
{
|
||||||
|
if (beatmapSkins == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed.");
|
||||||
|
|
||||||
|
return beatmapSkins.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowDrawableLookup(ISkinComponent component)
|
||||||
|
{
|
||||||
|
if (beatmapSkins == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed.");
|
||||||
|
|
||||||
|
return beatmapSkins.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowTextureLookup(string componentName)
|
||||||
|
{
|
||||||
|
if (beatmapSkins == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed.");
|
||||||
|
|
||||||
|
return beatmapSkins.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowSampleLookup(ISampleInfo componentName)
|
||||||
|
{
|
||||||
|
if (beatmapSkins == null)
|
||||||
|
throw new InvalidOperationException($"{nameof(BeatmapSkinProvidingContainer)} needs to be loaded before being consumed.");
|
||||||
|
|
||||||
|
return beatmapHitsounds.Value;
|
||||||
|
}
|
||||||
|
|
||||||
public BeatmapSkinProvidingContainer(ISkin skin)
|
public BeatmapSkinProvidingContainer(ISkin skin)
|
||||||
: base(skin)
|
: base(skin)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
{
|
||||||
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
|
var config = parent.Get<OsuConfigManager>();
|
||||||
config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds);
|
|
||||||
|
|
||||||
|
beatmapSkins = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
|
||||||
|
beatmapHitsounds = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
|
||||||
|
|
||||||
|
return base.CreateChildDependencies(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
beatmapSkins.BindValueChanged(_ => TriggerSourceChanged());
|
beatmapSkins.BindValueChanged(_ => TriggerSourceChanged());
|
||||||
beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged());
|
beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged());
|
||||||
}
|
}
|
||||||
|
51
osu.Game/Skinning/LegacyRollingCounter.cs
Normal file
@ -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 System;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An integer <see cref="RollingCounter{T}"/> that uses number sprites from a legacy skin.
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyRollingCounter : RollingCounter<int>
|
||||||
|
{
|
||||||
|
private readonly ISkin skin;
|
||||||
|
|
||||||
|
private readonly string fontName;
|
||||||
|
private readonly float fontOverlap;
|
||||||
|
|
||||||
|
protected override bool IsRollingProportional => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="LegacyRollingCounter"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin">The <see cref="ISkin"/> from which to get counter number sprites.</param>
|
||||||
|
/// <param name="fontName">The name of the legacy font to use.</param>
|
||||||
|
/// <param name="fontOverlap">
|
||||||
|
/// The numeric overlap of number sprites to use.
|
||||||
|
/// A positive number will bring the number sprites closer together, while a negative number
|
||||||
|
/// will split them apart more.
|
||||||
|
/// </param>
|
||||||
|
public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap)
|
||||||
|
{
|
||||||
|
this.skin = skin;
|
||||||
|
this.fontName = fontName;
|
||||||
|
this.fontOverlap = fontOverlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double GetProportionalDuration(int currentValue, int newValue)
|
||||||
|
{
|
||||||
|
return Math.Abs(newValue - currentValue) * 75.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override OsuSpriteText CreateSpriteText() =>
|
||||||
|
new LegacySpriteText(skin, fontName)
|
||||||
|
{
|
||||||
|
Spacing = new Vector2(-fontOverlap, 0f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ namespace osu.Game.Skinning
|
|||||||
public enum LegacySetting
|
public enum LegacySetting
|
||||||
{
|
{
|
||||||
Version,
|
Version,
|
||||||
|
ComboPrefix,
|
||||||
|
ComboOverlap,
|
||||||
AnimationFramerate,
|
AnimationFramerate,
|
||||||
LayeredHitSounds,
|
LayeredHitSounds,
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,9 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasFont(this ISkin source, string fontPrefix)
|
||||||
|
=> source.GetTexture($"{fontPrefix}-0") != null;
|
||||||
|
|
||||||
public class SkinnableTextureAnimation : TextureAnimation
|
public class SkinnableTextureAnimation : TextureAnimation
|
||||||
{
|
{
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
|
@ -114,6 +114,8 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
|
bool wasPlaying = IsPlaying;
|
||||||
|
|
||||||
var channels = hitSamples.Select(s =>
|
var channels = hitSamples.Select(s =>
|
||||||
{
|
{
|
||||||
var ch = skin.GetSample(s);
|
var ch = skin.GetSample(s);
|
||||||
@ -138,8 +140,9 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
||||||
|
|
||||||
if (requestedPlaying)
|
// Start playback internally for the new samples if the previous ones were playing beforehand.
|
||||||
Play();
|
if (wasPlaying)
|
||||||
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Re-expose AudioContainer
|
#region Re-expose AudioContainer
|
||||||
|
@ -160,6 +160,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
|
var lookup = base.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
|
|
||||||
|
if (lookup != null)
|
||||||
|
return lookup;
|
||||||
|
|
||||||
// extrapolate frames to test longer animations
|
// extrapolate frames to test longer animations
|
||||||
if (extrapolateAnimations)
|
if (extrapolateAnimations)
|
||||||
{
|
{
|
||||||
@ -169,7 +174,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT);
|
return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetTexture(componentName, wrapModeS, wrapModeT);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.911.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.923.1" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.6" />
|
<PackageReference Include="Sentry" Version="2.1.6" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.911.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.923.1" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
@ -80,7 +80,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.911.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.923.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|