1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 20:22:55 +08:00

Merge branch 'master' into dho-apply

This commit is contained in:
Dean Herbert 2019-10-18 19:45:10 +09:00 committed by GitHub
commit d0eaf629af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 602 additions and 67 deletions

View File

@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column == null) if (Column == null)
return base.OnMouseDown(e); return base.OnMouseDown(e);
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = Column.Index; HitObject.Column = Column.Index;
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition));
BeginPlacement();
return true; return true;
} }

View File

@ -65,24 +65,27 @@ namespace osu.Game.Rulesets.Mania.Edit
private void performDragMovement(MoveSelectionEvent moveEvent) private void performDragMovement(MoveSelectionEvent moveEvent)
{ {
float delta = moveEvent.InstantDelta.Y;
// When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
// This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
delta -= moveEvent.Blueprint.HitObject.Parent.DrawHeight;
foreach (var b in SelectedBlueprints) foreach (var b in SelectedBlueprints)
{ {
var hitObject = b.HitObject; var hitObject = b.HitObject;
var objectParent = (HitObjectContainer)hitObject.Parent; var objectParent = (HitObjectContainer)hitObject.Parent;
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame // StartTime could be used to adjust the position if only one movement event was received per frame.
// without the position having been updated by the parenting ScrollingHitObjectContainer // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
hitObject.Y += moveEvent.InstantDelta.Y; hitObject.Y += delta;
float targetPosition; float targetPosition = hitObject.Position.Y;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
// so we need to flip the vertical coordinate in the hitobject container's space
if (scrollingInfo.Direction.Value == ScrollingDirection.Down) if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -hitObject.Position.Y; targetPosition = -targetPosition;
else
targetPosition = hitObject.Position.Y;
objectParent.Remove(hitObject); objectParent.Remove(hitObject);

View File

@ -0,0 +1,210 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private TestOsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
createGrid();
}
[SetUp]
public void Setup() => Schedule(() =>
{
Clear();
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
beatDivisor.Value = 1;
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
createGrid();
}
[TestCase(100, 100)]
[TestCase(200, 100)]
public void TestBeatLength(float beatLength, float expectedSpacing)
{
AddStep($"set beat length = {beatLength}", () =>
{
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength });
});
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[TestCase(0.5f, 50)]
[TestCase(1, 100)]
[TestCase(1.5f, 150)]
public void TestSpeedMultiplier(float multiplier, float expectedSpacing)
{
AddStep($"set speed multiplier = {multiplier}", () =>
{
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier });
});
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[TestCase(0.5f, 50)]
[TestCase(1, 100)]
[TestCase(1.5f, 150)]
public void TestSliderMultiplier(float multiplier, float expectedSpacing)
{
AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier);
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[Test]
public void TestCursorInCentre()
{
createGrid();
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorBeforeMovementPoint()
{
createGrid();
AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorAfterMovementPoint()
{
createGrid();
AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f)));
assertSnappedDistance((float)beat_length * 2);
}
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
{
Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position));
float distance = Vector2.Distance(snappedPosition, grid_position);
return Precision.AlmostEquals(expectedDistance, distance);
});
private void createGrid()
{
AddStep("create grid", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) }
};
});
}
private class SnappingCursorContainer : CompositeDrawable
{
public Func<Vector2, Vector2> GetSnapPosition;
private readonly Drawable cursor;
public SnappingCursorContainer()
{
RelativeSizeAxes = Axes.Both;
InternalChild = cursor = new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(50),
Colour = Color4.Red
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
base.OnMouseMove(e);
updatePosition(e.ScreenSpaceMousePosition);
return true;
}
private void updatePosition(Vector2 screenSpacePosition)
{
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
}
}
private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject)
{
}
}
}
}

View File

@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
HitObject.StartTime = EditorClock.CurrentTime;
EndPlacement(); EndPlacement();
return true; return true;
} }

View File

@ -104,8 +104,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve() private void beginCurve()
{ {
BeginPlacement(); BeginPlacement();
HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body); setState(PlacementState.Body);
} }

View File

@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
} }
else else
{ {
HitObject.StartTime = EditorClock.CurrentTime;
isPlacingEnd = true; isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint); piece.FadeTo(1f, 150, Easing.OutQuint);

View File

@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
public OsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject, hitObject.StackedEndPosition)
{
}
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time);
double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
return (float)(scoringDistance / timingPoint.BeatLength);
}
}
}

View File

@ -14,8 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{ {
/// <summary>
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
/// </summary>
public const float OBJECT_RADIUS = 64; public const float OBJECT_RADIUS = 64;
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
/// </summary>
internal const float BASE_SCORING_DISTANCE = 100;
public double TimePreempt = 600; public double TimePreempt = 600;
public double TimeFadeIn = 400; public double TimeFadeIn = 400;

View File

@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class Slider : OsuHitObject, IHasCurve public class Slider : OsuHitObject, IHasCurve
{ {
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second.
/// </summary>
private const float base_scoring_distance = 100;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;

View File

@ -0,0 +1,143 @@
// 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 NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneHitObjectAccentColour : OsuTestScene
{
private Container skinContainer;
[SetUp]
public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin()));
[Test]
public void TestChangeComboIndexBeforeLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("set combo and add hitobject", () =>
{
hitObject = new TestDrawableHitObject();
hitObject.HitObject.ComboIndex = 1;
skinContainer.Add(hitObject);
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexDuringLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject and set combo", () =>
{
skinContainer.Add(hitObject = new TestDrawableHitObject());
hitObject.HitObject.ComboIndex = 1;
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexAfterLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject()));
AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red);
AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1);
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObjectWithCombo>
{
public TestDrawableHitObject()
: base(new TestHitObjectWithCombo())
{
}
}
private class TestHitObjectWithCombo : HitObject, IHasComboInformation
{
public bool NewCombo { get; } = false;
public int ComboOffset { get; } = 0;
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
public int IndexInCurrentCombo
{
get => IndexInCurrentComboBindable.Value;
set => IndexInCurrentComboBindable.Value = value;
}
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
public int ComboIndex
{
get => ComboIndexBindable.Value;
set => ComboIndexBindable.Value = value;
}
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
public bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
}
}
private class TestSkin : ISkin
{
public readonly List<Color4> ComboColours = new List<Color4>
{
Color4.Red,
Color4.Green
};
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case GlobalSkinConfiguration global:
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(ComboColours));
}
break;
}
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class BeatmapSetInfoEqualityTest
{
[Test]
public void TestOnlineWithOnline()
{
var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithDatabased()
{
var ourInfo = new BeatmapSetInfo { ID = 123 };
var otherInfo = new BeatmapSetInfo { ID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithOnline()
{
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestCheckNullID()
{
var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved };
var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved };
Assert.AreNotEqual(ourInfo, otherInfo);
}
}
}

View File

@ -285,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background
}); });
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
private class DummySongSelect : PlaySongSelect private class DummySongSelect : PlaySongSelect
{ {
protected override BackgroundScreen CreateBackground() protected override BackgroundScreen CreateBackground()

View File

@ -19,7 +19,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor namespace osu.Game.Tests.Visual.Editor
{ {
public class TestSceneBeatSnapGrid : EditorClockTestScene public class TestSceneDistanceSnapGrid : EditorClockTestScene
{ {
private const double beat_length = 100; private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384); private static readonly Vector2 grid_position = new Vector2(512, 384);
@ -27,9 +27,9 @@ namespace osu.Game.Tests.Visual.Editor
[Cached(typeof(IEditorBeatmap))] [Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap; private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
private TestBeatSnapGrid grid; private TestDistanceSnapGrid grid;
public TestSceneBeatSnapGrid() public TestSceneDistanceSnapGrid()
{ {
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.Editor
AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01));
} }
private void createGrid(Action<TestBeatSnapGrid> func = null, string description = null) private void createGrid(Action<TestDistanceSnapGrid> func = null, string description = null)
{ {
AddStep($"create grid {description ?? string.Empty}", () => AddStep($"create grid {description ?? string.Empty}", () =>
{ {
@ -123,20 +123,20 @@ namespace osu.Game.Tests.Visual.Editor
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray Colour = Color4.SlateGray
}, },
grid = new TestBeatSnapGrid(new HitObject(), grid_position) grid = new TestDistanceSnapGrid(new HitObject(), grid_position)
}; };
func?.Invoke(grid); func?.Invoke(grid);
}); });
} }
private class TestBeatSnapGrid : BeatSnapGrid private class TestDistanceSnapGrid : DistanceSnapGrid
{ {
public new float Velocity = 1; public new float Velocity = 1;
public new float DistanceSpacing => base.DistanceSpacing; public new float DistanceSpacing => base.DistanceSpacing;
public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
: base(hitObject, centrePosition) : base(hitObject, centrePosition)
{ {
} }

View File

@ -349,5 +349,11 @@ namespace osu.Game.Tests.Visual.SongSelect
DateAdded = DateTimeOffset.UtcNow, DateAdded = DateTimeOffset.UtcNow,
}; };
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
} }
} }

View File

@ -63,6 +63,21 @@ namespace osu.Game.Beatmaps
public bool Protected { get; set; } public bool Protected { get; set; }
public bool Equals(BeatmapSetInfo other) => OnlineBeatmapSetID == other?.OnlineBeatmapSetID; public bool Equals(BeatmapSetInfo other)
{
if (other == null)
return false;
if (ID != 0 && other.ID != 0)
return ID == other.ID;
if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue)
return OnlineBeatmapSetID == other.OnlineBeatmapSetID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
}
} }
} }

View File

@ -298,6 +298,12 @@ namespace osu.Game
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
RulesetStore?.Dispose();
}
private class OsuUserInputManager : UserInputManager private class OsuUserInputManager : UserInputManager
{ {
protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button)

View File

@ -121,7 +121,7 @@ namespace osu.Game.Overlays.AccountCreation
multiAccountExplanationText.AddText("? osu! has a policy of "); multiAccountExplanationText.AddText("? osu! has a policy of ");
multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow);
multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in ");
multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow);
multiAccountExplanationText.AddText("."); multiAccountExplanationText.AddText(".");
furtherAssistance.AddText("Need further assistance? Contact us via our "); furtherAssistance.AddText("Need further assistance? Contact us via our ");

View File

@ -57,7 +57,8 @@ namespace osu.Game.Rulesets.Edit
{ {
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty<Mod>())) drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty<Mod>()))
{ {
Clock = framedClock Clock = framedClock,
ProcessCustomClock = false
}; };
} }
catch (Exception e) catch (Exception e)

View File

@ -91,8 +91,10 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Signals that the placement of <see cref="HitObject"/> has started. /// Signals that the placement of <see cref="HitObject"/> has started.
/// </summary> /// </summary>
protected void BeginPlacement() /// <param name="startTime">The start time of <see cref="HitObject"/> at the placement point. If null, the current clock time is used.</param>
protected void BeginPlacement(double? startTime = null)
{ {
HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
placementHandler.BeginPlacement(HitObject); placementHandler.BeginPlacement(HitObject);
PlacementBegun = true; PlacementBegun = true;
} }

View File

@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject is IHasComboInformation combo) if (HitObject is IHasComboInformation combo)
{ {
comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy();
comboIndexBindable.BindValueChanged(_ => updateAccentColour()); comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true);
} }
updateState(ArmedState.Idle, true); updateState(ArmedState.Idle, true);

View File

@ -11,28 +11,22 @@ using osu.Game.Database;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
{ {
/// <summary> public class RulesetStore : DatabaseBackedStore, IDisposable
/// Todo: All of this needs to be moved to a RulesetStore.
/// </summary>
public class RulesetStore : DatabaseBackedStore
{ {
private static readonly Dictionary<Assembly, Type> loaded_assemblies = new Dictionary<Assembly, Type>(); private const string ruleset_library_prefix = "osu.Game.Rulesets";
static RulesetStore() private readonly Dictionary<Assembly, Type> loadedAssemblies = new Dictionary<Assembly, Type>();
{
AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
// On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain();
loadFromDisk();
}
public RulesetStore(IDatabaseContextFactory factory) public RulesetStore(IDatabaseContextFactory factory)
: base(factory) : base(factory)
{ {
// On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain();
loadFromDisk();
addMissingRulesets(); addMissingRulesets();
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly;
} }
/// <summary> /// <summary>
@ -54,9 +48,7 @@ namespace osu.Game.Rulesets
/// </summary> /// </summary>
public IEnumerable<RulesetInfo> AvailableRulesets { get; private set; } public IEnumerable<RulesetInfo> AvailableRulesets { get; private set; }
private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
private const string ruleset_library_prefix = "osu.Game.Rulesets";
private void addMissingRulesets() private void addMissingRulesets()
{ {
@ -64,7 +56,7 @@ namespace osu.Game.Rulesets
{ {
var context = usage.Context; var context = usage.Context;
var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
//add all legacy modes in correct order //add all legacy modes in correct order
foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID))
@ -113,7 +105,7 @@ namespace osu.Game.Rulesets
} }
} }
private static void loadFromAppDomain() private void loadFromAppDomain()
{ {
foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies())
{ {
@ -126,7 +118,7 @@ namespace osu.Game.Rulesets
} }
} }
private static void loadFromDisk() private void loadFromDisk()
{ {
try try
{ {
@ -141,11 +133,11 @@ namespace osu.Game.Rulesets
} }
} }
private static void loadRulesetFromFile(string file) private void loadRulesetFromFile(string file)
{ {
var filename = Path.GetFileNameWithoutExtension(file); var filename = Path.GetFileNameWithoutExtension(file);
if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) if (loadedAssemblies.Values.Any(t => t.Namespace == filename))
return; return;
try try
@ -158,19 +150,30 @@ namespace osu.Game.Rulesets
} }
} }
private static void addRuleset(Assembly assembly) private void addRuleset(Assembly assembly)
{ {
if (loaded_assemblies.ContainsKey(assembly)) if (loadedAssemblies.ContainsKey(assembly))
return; return;
try try
{ {
loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error(e, $"Failed to add ruleset {assembly}"); Logger.Error(e, $"Failed to add ruleset {assembly}");
} }
} }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly;
}
} }
} }

View File

@ -0,0 +1,59 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
{
protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
: base(hitObject, centrePosition)
{
}
protected override void CreateContent(Vector2 centrePosition)
{
float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X);
float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y);
float maxDistance = new Vector2(dx, dy).Length;
int requiredCircles = (int)(maxDistance / DistanceSpacing);
for (int i = 0; i < requiredCircles; i++)
{
float radius = (i + 1) * DistanceSpacing * 2;
AddInternal(new CircularProgress
{
Origin = Anchor.Centre,
Position = centrePosition,
Current = { Value = 1 },
Size = new Vector2(radius),
InnerRadius = 4 * 1f / radius,
Colour = GetColourForBeatIndex(i)
});
}
}
public override Vector2 GetSnapPosition(Vector2 position)
{
Vector2 direction = position - CentrePosition;
if (direction == Vector2.Zero)
direction = new Vector2(0.001f, 0.001f);
float distance = direction.Length;
float radius = DistanceSpacing;
int radialCount = Math.Max(1, (int)Math.Round(distance / radius));
Vector2 normalisedDirection = direction * new Vector2(1f / distance);
return CentrePosition + normalisedDirection * radialCount * radius;
}
}
}

View File

@ -15,7 +15,10 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
public abstract class BeatSnapGrid : CompositeDrawable /// <summary>
/// A grid which takes user input and returns a quantized ("snapped") position and time.
/// </summary>
public abstract class DistanceSnapGrid : CompositeDrawable
{ {
/// <summary> /// <summary>
/// The velocity of the beatmap at the point of placement in pixels per millisecond. /// The velocity of the beatmap at the point of placement in pixels per millisecond.
@ -48,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private double startTime; private double startTime;
private double beatLength; private double beatLength;
protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition) protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
{ {
this.hitObject = hitObject; this.hitObject = hitObject;
this.CentrePosition = centrePosition; this.CentrePosition = centrePosition;
@ -114,14 +117,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// Snaps a position to this grid. /// Snaps a position to this grid.
/// </summary> /// </summary>
/// <param name="position">The original position in coordinate space local to this <see cref="BeatSnapGrid"/>.</param> /// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param>
/// <returns>The snapped position in coordinate space local to this <see cref="BeatSnapGrid"/>.</returns> /// <returns>The snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</returns>
public abstract Vector2 GetSnapPosition(Vector2 position); public abstract Vector2 GetSnapPosition(Vector2 position);
/// <summary> /// <summary>
/// Retrieves the time at a snapped position. /// Retrieves the time at a snapped position.
/// </summary> /// </summary>
/// <param name="position">The snapped position in coordinate space local to this <see cref="BeatSnapGrid"/>.</param> /// <param name="position">The snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param>
/// <returns>The time at the snapped position.</returns> /// <returns>The time at the snapped position.</returns>
public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity;

View File

@ -173,6 +173,12 @@ namespace osu.Game.Screens.Edit
bottomBackground.Colour = colours.Gray2; bottomBackground.Colour = colours.Gray2;
} }
protected override void Update()
{
base.Update();
clock.ProcessFrame();
}
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
switch (e.Key) switch (e.Key)