mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Merge branch 'master' into slider-timeline-velcotiy-adjust-v2
This commit is contained in:
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1015.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1014.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1014.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
|
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// 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.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
|
||||||
|
/// </summary>
|
||||||
|
private const float min_alpha = 0.0002f;
|
||||||
|
|
||||||
|
private const float transition_duration = 100;
|
||||||
|
|
||||||
|
public override string Name => "No Scope";
|
||||||
|
public override string Acronym => "NS";
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
|
||||||
|
public override string Description => "Where's the cursor?";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
private BindableNumber<int> currentCombo;
|
||||||
|
|
||||||
|
private float targetAlpha;
|
||||||
|
|
||||||
|
[SettingSource(
|
||||||
|
"Hidden at combo",
|
||||||
|
"The combo count at which the cursor becomes completely hidden",
|
||||||
|
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||||
|
)]
|
||||||
|
public BindableInt HiddenComboCount { get; } = new BindableInt
|
||||||
|
{
|
||||||
|
Default = 10,
|
||||||
|
Value = 10,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
|
|
||||||
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
if (HiddenComboCount.Value == 0) return;
|
||||||
|
|
||||||
|
currentCombo = scoreProcessor.Combo.GetBoundCopy();
|
||||||
|
currentCombo.BindValueChanged(combo =>
|
||||||
|
{
|
||||||
|
targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HiddenComboSlider : OsuSliderBar<int>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModBarrelRoll(),
|
new OsuModBarrelRoll(),
|
||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
|
new OsuModNoScope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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.Game.Models;
|
||||||
|
using osu.Game.Stores;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
public class RulesetStoreTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStore()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
var rulesets2 = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||||
|
|
||||||
|
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRetrievedRulesetsAreDetached()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -30,23 +31,35 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
PushAndConfirm(() => new EditorLoader());
|
PushAndConfirm(() => new EditorLoader());
|
||||||
|
|
||||||
AddUntilStep("wait for editor load", () => editor != null);
|
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
||||||
|
|
||||||
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
||||||
|
|
||||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
|
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||||
|
AddStep("Set artist and title", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||||
|
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||||
|
});
|
||||||
|
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty");
|
||||||
|
|
||||||
|
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||||
|
|
||||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
checkMutations();
|
||||||
|
|
||||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||||
|
|
||||||
|
checkMutations();
|
||||||
|
|
||||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||||
|
|
||||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
@ -58,8 +71,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
||||||
|
|
||||||
AddUntilStep("Wait for editor load", () => editor != null);
|
AddUntilStep("Wait for editor load", () => editor != null);
|
||||||
|
|
||||||
|
checkMutations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMutations()
|
||||||
|
{
|
||||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||||
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||||
|
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||||
|
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Database;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportCreatedNotification()
|
public void TestImportCreatedNotification()
|
||||||
{
|
{
|
||||||
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1);
|
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ProgressCompletionNotification>().Count() == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -19,28 +23,62 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
AddStep("create component", () =>
|
AddStep("create component", () =>
|
||||||
{
|
{
|
||||||
LabelledSliderBar<double> component;
|
FillFlowContainer flow;
|
||||||
|
|
||||||
Child = new Container
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Width = 500,
|
Width = 500,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = component = new LabelledSliderBar<double>
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new LabelledSliderBar<double>
|
||||||
{
|
{
|
||||||
Current = new BindableDouble(5)
|
Current = new BindableDouble(5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
},
|
||||||
}
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
component.Label = "a sample component";
|
foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Current = new BindableDouble(5)
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneSettingsCheckbox : OsuTestScene
|
||||||
|
{
|
||||||
|
[TestCase]
|
||||||
|
public void TestCheckbox()
|
||||||
|
{
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
FillFlowContainer flow;
|
||||||
|
|
||||||
|
Child = flow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var colour1 in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour1)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -176,11 +176,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the user requests to view the resulting import.
|
|
||||||
/// </summary>
|
|
||||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a beatmap difficulty.
|
/// Delete a beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -338,5 +333,14 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||||
|
|
||||||
|
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
|
||||||
|
{
|
||||||
|
set => beatmapModelManager.PostImport = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,13 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
var setInfo = beatmapInfo.BeatmapSet;
|
var setInfo = beatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
|
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
|
||||||
|
// This should hopefully be temporary, assuming said clone is eventually removed.
|
||||||
|
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
|
||||||
|
|
||||||
|
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
||||||
|
beatmapContent.BeatmapInfo = beatmapInfo;
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
@ -202,7 +209,6 @@ namespace osu.Game.Beatmaps
|
|||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
|
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
|
||||||
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
|
|
||||||
|
|
||||||
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
||||||
|
|
||||||
|
@ -188,6 +188,8 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
|
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CancelAsyncLoad()
|
public void CancelAsyncLoad()
|
||||||
|
{
|
||||||
|
lock (beatmapFetchLock)
|
||||||
{
|
{
|
||||||
loadCancellation?.Cancel();
|
loadCancellation?.Cancel();
|
||||||
loadCancellation = new CancellationTokenSource();
|
loadCancellation = new CancellationTokenSource();
|
||||||
@ -195,6 +197,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapLoadTask?.IsCompleted != true)
|
if (beatmapLoadTask?.IsCompleted != true)
|
||||||
beatmapLoadTask = null;
|
beatmapLoadTask = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
|
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
@ -205,7 +208,13 @@ namespace osu.Game.Beatmaps
|
|||||||
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
|
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<IBeatmap> loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
|
private readonly object beatmapFetchLock = new object();
|
||||||
|
|
||||||
|
private Task<IBeatmap> loadBeatmapAsync()
|
||||||
|
{
|
||||||
|
lock (beatmapFetchLock)
|
||||||
|
{
|
||||||
|
return beatmapLoadTask ??= Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
// Todo: Handle cancellation during beatmap parsing
|
// Todo: Handle cancellation during beatmap parsing
|
||||||
var b = GetBeatmap() ?? new Beatmap();
|
var b = GetBeatmap() ?? new Beatmap();
|
||||||
@ -218,6 +227,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
return b;
|
return b;
|
||||||
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => BeatmapInfo.ToString();
|
public override string ToString() => BeatmapInfo.ToString();
|
||||||
|
|
||||||
|
@ -66,10 +66,14 @@ namespace osu.Game.Beatmaps
|
|||||||
lock (workingCache)
|
lock (workingCache)
|
||||||
{
|
{
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
||||||
|
|
||||||
if (working != null)
|
if (working != null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Invalidating working beatmap cache for {info}");
|
||||||
workingCache.Remove(working);
|
workingCache.Remove(working);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -86,6 +90,7 @@ namespace osu.Game.Beatmaps
|
|||||||
lock (workingCache)
|
lock (workingCache)
|
||||||
{
|
{
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||||
|
|
||||||
if (working != null)
|
if (working != null)
|
||||||
return working;
|
return working;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPostImports<TModel>
|
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
|
||||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||||
where TFileModel : class, INamedFileInfo, new()
|
where TFileModel : class, INamedFileInfo, new()
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Database
|
|||||||
/// A class which handles importing of associated models to the game store.
|
/// A class which handles importing of associated models to the game store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
public interface IModelImporter<TModel> : IPostNotifications
|
public interface IModelImporter<TModel> : IPostNotifications, IPostImports<TModel>
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Statistics;
|
using osu.Framework.Statistics;
|
||||||
@ -18,7 +17,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RealmContextFactory : Component, IRealmFactory
|
public class RealmContextFactory : IDisposable, IRealmFactory
|
||||||
{
|
{
|
||||||
private readonly Storage storage;
|
private readonly Storage storage;
|
||||||
|
|
||||||
@ -79,10 +78,11 @@ namespace osu.Game.Database
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool Compact() => Realm.Compact(getConfiguration());
|
public bool Compact() => Realm.Compact(getConfiguration());
|
||||||
|
|
||||||
protected override void Update()
|
/// <summary>
|
||||||
|
/// Perform a blocking refresh on the main realm context.
|
||||||
|
/// </summary>
|
||||||
|
public void Refresh()
|
||||||
{
|
{
|
||||||
base.Update();
|
|
||||||
|
|
||||||
lock (contextLock)
|
lock (contextLock)
|
||||||
{
|
{
|
||||||
if (context?.Refresh() == true)
|
if (context?.Refresh() == true)
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public Realm CreateContext()
|
public Realm CreateContext()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Database
|
|||||||
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
||||||
public IDisposable BlockAllOperations()
|
public IDisposable BlockAllOperations()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
if (!ThreadSafety.IsUpdateThread)
|
if (!ThreadSafety.IsUpdateThread)
|
||||||
@ -176,21 +176,23 @@ namespace osu.Game.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
private bool isDisposed;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (contextLock)
|
lock (contextLock)
|
||||||
{
|
{
|
||||||
context?.Dispose();
|
context?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsDisposed)
|
if (!isDisposed)
|
||||||
{
|
{
|
||||||
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
||||||
contextCreationLock.Wait();
|
contextCreationLock.Wait();
|
||||||
contextCreationLock.Dispose();
|
contextCreationLock.Dispose();
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
isDisposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 JetBrains.Annotations;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -12,31 +13,39 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class Nub : CircularContainer, IHasCurrentValue<bool>, IHasAccentColour
|
public class Nub : CompositeDrawable, IHasCurrentValue<bool>, IHasAccentColour
|
||||||
{
|
{
|
||||||
public const float COLLAPSED_SIZE = 20;
|
public const float HEIGHT = 15;
|
||||||
public const float EXPANDED_SIZE = 40;
|
|
||||||
|
public const float EXPANDED_SIZE = 50;
|
||||||
|
|
||||||
private const float border_width = 3;
|
private const float border_width = 3;
|
||||||
|
|
||||||
private const double animate_in_duration = 150;
|
private const double animate_in_duration = 200;
|
||||||
private const double animate_out_duration = 500;
|
private const double animate_out_duration = 500;
|
||||||
|
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly Container main;
|
||||||
|
|
||||||
public Nub()
|
public Nub()
|
||||||
{
|
{
|
||||||
Box fill;
|
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||||
|
|
||||||
Size = new Vector2(COLLAPSED_SIZE, 12);
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
BorderColour = Color4.White;
|
main = new CircularContainer
|
||||||
BorderThickness = border_width;
|
{
|
||||||
|
BorderColour = Color4.White,
|
||||||
Masking = true;
|
BorderThickness = border_width,
|
||||||
|
Masking = true,
|
||||||
Children = new[]
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
fill = new Box
|
fill = new Box
|
||||||
{
|
{
|
||||||
@ -44,31 +53,34 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
},
|
||||||
Current.ValueChanged += filled =>
|
|
||||||
{
|
|
||||||
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
|
||||||
this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuColour colours)
|
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
AccentColour = colours.Pink;
|
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||||
GlowingAccentColour = colours.PinkLighter;
|
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
|
||||||
GlowColour = colours.PinkDarker;
|
GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
|
||||||
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
main.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Colour = GlowColour.Opacity(0),
|
Colour = GlowColour.Opacity(0),
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Radius = 10,
|
Radius = 8,
|
||||||
Roundness = 8,
|
Roundness = 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
private bool glowing;
|
private bool glowing;
|
||||||
|
|
||||||
public bool Glowing
|
public bool Glowing
|
||||||
@ -80,28 +92,17 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
main.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||||
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint);
|
main.FadeEdgeEffectTo(0.2f, animate_in_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FadeEdgeEffectTo(0, animate_out_duration);
|
main.FadeEdgeEffectTo(0, animate_out_duration, Easing.OutQuint);
|
||||||
this.FadeColour(AccentColour, animate_out_duration);
|
main.FadeColour(AccentColour, animate_out_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Expanded
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
this.ResizeTo(new Vector2(EXPANDED_SIZE, 12), animate_in_duration, Easing.OutQuint);
|
|
||||||
else
|
|
||||||
this.ResizeTo(new Vector2(COLLAPSED_SIZE, 12), animate_out_duration, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Bindable<bool> current = new Bindable<bool>();
|
private readonly Bindable<bool> current = new Bindable<bool>();
|
||||||
|
|
||||||
public Bindable<bool> Current
|
public Bindable<bool> Current
|
||||||
@ -126,7 +127,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
if (!Glowing)
|
if (!Glowing)
|
||||||
Colour = value;
|
main.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
glowingAccentColour = value;
|
glowingAccentColour = value;
|
||||||
if (Glowing)
|
if (Glowing)
|
||||||
Colour = value;
|
main.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,10 +153,22 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
glowColour = value;
|
glowColour = value;
|
||||||
|
|
||||||
var effect = EdgeEffect;
|
var effect = main.EdgeEffect;
|
||||||
effect.Colour = Glowing ? value : value.Opacity(0);
|
effect.Colour = Glowing ? value : value.Opacity(0);
|
||||||
EdgeEffect = effect;
|
main.EdgeEffect = effect;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCurrentValueChanged(ValueChangedEvent<bool> filled)
|
||||||
|
{
|
||||||
|
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (filled.NewValue)
|
||||||
|
main.ResizeWidthTo(1, animate_in_duration, Easing.OutElasticHalf);
|
||||||
|
else
|
||||||
|
main.ResizeWidthTo(0.9f, animate_out_duration, Easing.OutElastic);
|
||||||
|
|
||||||
|
main.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,11 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class OsuCheckbox : Checkbox
|
public class OsuCheckbox : Checkbox
|
||||||
{
|
{
|
||||||
public Color4 CheckedColor { get; set; } = Color4.Cyan;
|
|
||||||
public Color4 UncheckedColor { get; set; } = Color4.White;
|
|
||||||
public int FadeDuration { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to play sounds when the state changes as a result of user interaction.
|
/// Whether to play sounds when the state changes as a result of user interaction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -104,14 +99,12 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = true;
|
Nub.Glowing = true;
|
||||||
Nub.Expanded = true;
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = false;
|
Nub.Glowing = false;
|
||||||
Nub.Expanded = false;
|
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -16,6 +18,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
@ -52,35 +55,64 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
leftBox.Colour = value;
|
leftBox.Colour = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Colour4 backgroundColour;
|
||||||
|
|
||||||
|
public Color4 BackgroundColour
|
||||||
|
{
|
||||||
|
get => backgroundColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
backgroundColour = value;
|
||||||
rightBox.Colour = value;
|
rightBox.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuSliderBar()
|
public OsuSliderBar()
|
||||||
{
|
{
|
||||||
Height = 12;
|
Height = Nub.HEIGHT;
|
||||||
RangePadding = 20;
|
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Padding = new MarginPadding { Horizontal = 2 },
|
||||||
|
Child = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5f,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftBox = new Box
|
leftBox = new Box
|
||||||
{
|
{
|
||||||
Height = 2,
|
Height = 5,
|
||||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||||
Position = new Vector2(2, 0),
|
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
rightBox = new Box
|
rightBox = new Box
|
||||||
{
|
{
|
||||||
Height = 2,
|
Height = 5,
|
||||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||||
Position = new Vector2(-2, 0),
|
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
nubContainer = new Container
|
nubContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -88,7 +120,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativePositionAxes = Axes.X,
|
RelativePositionAxes = Axes.X,
|
||||||
Expanded = true,
|
Current = { Value = true }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
new HoverClickSounds()
|
||||||
@ -97,11 +129,12 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, OsuColour colours)
|
private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||||
AccentColour = colours.Pink;
|
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||||
|
BackgroundColour = colourProvider?.Background5 ?? colours.Pink.Opacity(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -119,26 +152,25 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = true;
|
updateGlow();
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = false;
|
updateGlow();
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
Nub.Current.Value = true;
|
updateGlow();
|
||||||
return base.OnMouseDown(e);
|
base.OnDragEnd(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
private void updateGlow()
|
||||||
{
|
{
|
||||||
Nub.Current.Value = false;
|
Nub.Glowing = IsHovered || IsDragged;
|
||||||
base.OnMouseUp(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserChange(T value)
|
protected override void OnUserChange(T value)
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
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.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
@ -23,10 +25,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuColour colours)
|
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
BackgroundColour = colours.Blue3;
|
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
UserJoined?.Invoke(user);
|
UserJoined?.Invoke(user);
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}, false);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
||||||
|
@ -554,6 +554,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
beatmap.OldValue?.CancelAsyncLoad();
|
beatmap.OldValue?.CancelAsyncLoad();
|
||||||
beatmap.NewValue?.BeginAsyncLoad();
|
beatmap.NewValue?.BeginAsyncLoad();
|
||||||
|
Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
@ -642,7 +643,7 @@ namespace osu.Game
|
|||||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
|
BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value);
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => Notifications.Post(n);
|
ScoreManager.PostNotification = n => Notifications.Post(n);
|
||||||
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
||||||
|
@ -187,8 +187,6 @@ namespace osu.Game
|
|||||||
|
|
||||||
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
||||||
|
|
||||||
AddInternal(realmFactory);
|
|
||||||
|
|
||||||
dependencies.CacheAs(Storage);
|
dependencies.CacheAs(Storage);
|
||||||
|
|
||||||
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||||
@ -529,6 +527,7 @@ namespace osu.Game
|
|||||||
LocalConfig?.Dispose();
|
LocalConfig?.Dispose();
|
||||||
|
|
||||||
contextFactory?.FlushConnections();
|
contextFactory?.FlushConnections();
|
||||||
|
realmFactory?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
protected override Drawable CreateControl() => new TSlider
|
protected override Drawable CreateControl() => new TSlider
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
Margin = new MarginPadding { Vertical = 10 },
|
||||||
RelativeSizeAxes = Axes.X
|
RelativeSizeAxes = Axes.X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,9 +47,34 @@ namespace osu.Game.Rulesets.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly HashSet<TLookup> pendingWrites = new HashSet<TLookup>();
|
||||||
|
|
||||||
protected override bool PerformSave()
|
protected override bool PerformSave()
|
||||||
{
|
{
|
||||||
// do nothing, realm saves immediately
|
TLookup[] changed;
|
||||||
|
|
||||||
|
lock (pendingWrites)
|
||||||
|
{
|
||||||
|
changed = pendingWrites.ToArray();
|
||||||
|
pendingWrites.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realmFactory == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
using (var context = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
context.Write(realm =>
|
||||||
|
{
|
||||||
|
foreach (var c in changed)
|
||||||
|
{
|
||||||
|
var setting = realm.All<RealmRulesetSetting>().First(s => s.RulesetID == rulesetId && s.Variant == variant && s.Key == c.ToString());
|
||||||
|
|
||||||
|
setting.Value = ConfigStore[c].ToString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +105,8 @@ namespace osu.Game.Rulesets.Configuration
|
|||||||
|
|
||||||
bindable.ValueChanged += b =>
|
bindable.ValueChanged += b =>
|
||||||
{
|
{
|
||||||
realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString());
|
lock (pendingWrites)
|
||||||
|
pendingWrites.Add(lookup);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles, IPostImports<ScoreInfo>
|
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles
|
||||||
{
|
{
|
||||||
private readonly Scheduler scheduler;
|
private readonly Scheduler scheduler;
|
||||||
private readonly Func<BeatmapDifficultyCache> difficulties;
|
private readonly Func<BeatmapDifficultyCache> difficulties;
|
||||||
|
@ -162,7 +162,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
// todo: remove caching of this and consume via editorBeatmap?
|
// todo: remove caching of this and consume via editorBeatmap?
|
||||||
dependencies.Cache(beatDivisor);
|
dependencies.Cache(beatDivisor);
|
||||||
|
|
||||||
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin()));
|
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin(), loadableBeatmap.BeatmapInfo));
|
||||||
dependencies.CacheAs(editorBeatmap);
|
dependencies.CacheAs(editorBeatmap);
|
||||||
changeHandler = new EditorChangeHandler(editorBeatmap);
|
changeHandler = new EditorChangeHandler(editorBeatmap);
|
||||||
dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
|
dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
|
||||||
@ -333,10 +333,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
isNewBeatmap = false;
|
isNewBeatmap = false;
|
||||||
|
|
||||||
// apply any set-level metadata changes.
|
// apply any set-level metadata changes.
|
||||||
beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet);
|
beatmapManager.Update(editorBeatmap.BeatmapInfo.BeatmapSet);
|
||||||
|
|
||||||
// save the loaded beatmap's data stream.
|
// save the loaded beatmap's data stream.
|
||||||
beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
|
beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin);
|
||||||
|
|
||||||
updateLastSavedHash();
|
updateLastSavedHash();
|
||||||
}
|
}
|
||||||
@ -523,7 +523,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
||||||
|
{
|
||||||
|
Logger.Log("Editor providing re-fetched beatmap post edit session");
|
||||||
Beatmap.Value = refetchedBeatmap;
|
Beatmap.Value = refetchedBeatmap;
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Bindable<HitObject> PlacementObject = new Bindable<HitObject>();
|
public readonly Bindable<HitObject> PlacementObject = new Bindable<HitObject>();
|
||||||
|
|
||||||
|
private readonly BeatmapInfo beatmapInfo;
|
||||||
public readonly IBeatmap PlayableBeatmap;
|
public readonly IBeatmap PlayableBeatmap;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -67,7 +68,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>();
|
private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>();
|
||||||
|
|
||||||
public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
|
public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null)
|
||||||
{
|
{
|
||||||
PlayableBeatmap = playableBeatmap;
|
PlayableBeatmap = playableBeatmap;
|
||||||
|
|
||||||
@ -96,6 +97,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
playableBeatmap.ControlPointInfo = newControlPoints;
|
playableBeatmap.ControlPointInfo = newControlPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo;
|
||||||
|
|
||||||
if (beatmapSkin is Skin skin)
|
if (beatmapSkin is Skin skin)
|
||||||
BeatmapSkin = new EditorBeatmapSkin(skin);
|
BeatmapSkin = new EditorBeatmapSkin(skin);
|
||||||
|
|
||||||
@ -107,11 +110,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public BeatmapInfo BeatmapInfo
|
public BeatmapInfo BeatmapInfo
|
||||||
{
|
{
|
||||||
get => PlayableBeatmap.BeatmapInfo;
|
get => beatmapInfo;
|
||||||
set => PlayableBeatmap.BeatmapInfo = value;
|
set => throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeatmapMetadata Metadata => PlayableBeatmap.Metadata;
|
public BeatmapMetadata Metadata => beatmapInfo.Metadata;
|
||||||
|
|
||||||
public BeatmapDifficulty Difficulty
|
public BeatmapDifficulty Difficulty
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterfaceV2;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Setup
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
{
|
{
|
||||||
internal class MetadataSection : SetupSection
|
public class MetadataSection : SetupSection
|
||||||
{
|
{
|
||||||
protected LabelledTextBox ArtistTextBox;
|
protected LabelledTextBox ArtistTextBox;
|
||||||
protected LabelledTextBox RomanisedArtistTextBox;
|
protected LabelledTextBox RomanisedArtistTextBox;
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using ManagedBass.Fx;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@ -35,6 +36,7 @@ namespace osu.Game.Screens.Play
|
|||||||
private Track track;
|
private Track track;
|
||||||
|
|
||||||
private AudioFilter failLowPassFilter;
|
private AudioFilter failLowPassFilter;
|
||||||
|
private AudioFilter failHighPassFilter;
|
||||||
|
|
||||||
private const float duration = 2500;
|
private const float duration = 2500;
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ namespace osu.Game.Screens.Play
|
|||||||
failSample = audio.Samples.Get(@"Gameplay/failsound");
|
failSample = audio.Samples.Get(@"Gameplay/failsound");
|
||||||
|
|
||||||
AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer));
|
AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer));
|
||||||
|
AddInternal(failHighPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool started;
|
private bool started;
|
||||||
@ -66,14 +69,14 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
|
|
||||||
failSample.Play();
|
|
||||||
|
|
||||||
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
|
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
|
||||||
{
|
{
|
||||||
OnComplete?.Invoke();
|
OnComplete?.Invoke();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
failHighPassFilter.CutoffTo(300);
|
||||||
failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic);
|
failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic);
|
||||||
|
failSample.Play();
|
||||||
|
|
||||||
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
|
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
Nub.AccentColour = colours.Yellow;
|
Nub.AccentColour = colours.Yellow;
|
||||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||||
Nub.GlowColour = colours.YellowDarker;
|
Nub.GlowColour = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
AccentColour = colours.Yellow;
|
AccentColour = colours.Yellow;
|
||||||
Nub.AccentColour = colours.Yellow;
|
Nub.AccentColour = colours.Yellow;
|
||||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||||
Nub.GlowColour = colours.YellowDarker;
|
Nub.GlowColour = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -410,7 +410,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return;
|
if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return;
|
||||||
|
|
||||||
Logger.Log($"working beatmap updated to {e.NewValue}");
|
Logger.Log($"Song select working beatmap updated to {e.NewValue}");
|
||||||
|
|
||||||
if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false))
|
if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false))
|
||||||
{
|
{
|
||||||
|
263
osu.Game/Stores/RealmRulesetStore.cs
Normal file
263
osu.Game/Stores/RealmRulesetStore.cs
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Stores
|
||||||
|
{
|
||||||
|
public class RealmRulesetStore : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RealmContextFactory realmFactory;
|
||||||
|
|
||||||
|
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
|
||||||
|
|
||||||
|
private readonly Dictionary<Assembly, Type> loadedAssemblies = new Dictionary<Assembly, Type>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All available rulesets.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<IRulesetInfo> AvailableRulesets => availableRulesets;
|
||||||
|
|
||||||
|
private readonly List<IRulesetInfo> availableRulesets = new List<IRulesetInfo>();
|
||||||
|
|
||||||
|
public RealmRulesetStore(RealmContextFactory realmFactory, Storage? storage = null)
|
||||||
|
{
|
||||||
|
this.realmFactory = realmFactory;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// This null check prevents Android from attempting to load the rulesets from disk,
|
||||||
|
// as the underlying path "AppContext.BaseDirectory", despite being non-nullable, it returns null on android.
|
||||||
|
// See https://github.com/xamarin/xamarin-android/issues/3489.
|
||||||
|
if (RuntimeInfo.StartupDirectory != null)
|
||||||
|
loadFromDisk();
|
||||||
|
|
||||||
|
// the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory.
|
||||||
|
// It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail
|
||||||
|
// to load as unable to locate the game core assembly.
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
|
||||||
|
|
||||||
|
var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
|
||||||
|
if (rulesetStorage != null)
|
||||||
|
loadUserRulesets(rulesetStorage);
|
||||||
|
|
||||||
|
addMissingRulesets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a ruleset using a known ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The ruleset's internal ID.</param>
|
||||||
|
/// <returns>A ruleset, if available, else null.</returns>
|
||||||
|
public IRulesetInfo? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a ruleset using a known short name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shortName">The ruleset's short name.</param>
|
||||||
|
/// <returns>A ruleset, if available, else null.</returns>
|
||||||
|
public IRulesetInfo? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName);
|
||||||
|
|
||||||
|
private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args)
|
||||||
|
{
|
||||||
|
var asm = new AssemblyName(args.Name);
|
||||||
|
|
||||||
|
// the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies.
|
||||||
|
// this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name
|
||||||
|
// already loaded in the AppDomain.
|
||||||
|
var domainAssembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
// Given name is always going to be equally-or-more qualified than the assembly name.
|
||||||
|
.Where(a =>
|
||||||
|
{
|
||||||
|
string? name = a.GetName().Name;
|
||||||
|
if (name == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return args.Name.Contains(name, StringComparison.Ordinal);
|
||||||
|
})
|
||||||
|
// Pick the greatest assembly version.
|
||||||
|
.OrderByDescending(a => a.GetName().Version)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (domainAssembly != null)
|
||||||
|
return domainAssembly;
|
||||||
|
|
||||||
|
return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMissingRulesets()
|
||||||
|
{
|
||||||
|
realmFactory.Context.Write(realm =>
|
||||||
|
{
|
||||||
|
var rulesets = realm.All<RealmRuleset>();
|
||||||
|
|
||||||
|
List<Ruleset> instances = loadedAssemblies.Values
|
||||||
|
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||||
|
.Where(r => r != null)
|
||||||
|
.Select(r => r.AsNonNull())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||||
|
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||||
|
{
|
||||||
|
if (realm.All<RealmRuleset>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.ID) == null)
|
||||||
|
realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add any other rulesets which have assemblies present but are not yet in the database.
|
||||||
|
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||||
|
{
|
||||||
|
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||||
|
{
|
||||||
|
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
||||||
|
|
||||||
|
if (existingSameShortName != null)
|
||||||
|
{
|
||||||
|
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
||||||
|
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
||||||
|
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
||||||
|
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RealmRuleset> detachedRulesets = new List<RealmRuleset>();
|
||||||
|
|
||||||
|
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
||||||
|
foreach (var r in rulesets)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var type = Type.GetType(r.InstantiationInfo);
|
||||||
|
|
||||||
|
if (type == null)
|
||||||
|
throw new InvalidOperationException(@"Type resolution failure.");
|
||||||
|
|
||||||
|
var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo;
|
||||||
|
|
||||||
|
if (rInstance == null)
|
||||||
|
throw new InvalidOperationException(@"Instantiation failure.");
|
||||||
|
|
||||||
|
r.Name = rInstance.Name;
|
||||||
|
r.ShortName = rInstance.ShortName;
|
||||||
|
r.InstantiationInfo = rInstance.InstantiationInfo;
|
||||||
|
r.Available = true;
|
||||||
|
|
||||||
|
detachedRulesets.Add(r.Clone());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
r.Available = false;
|
||||||
|
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableRulesets.AddRange(detachedRulesets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromAppDomain()
|
||||||
|
{
|
||||||
|
foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
string? rulesetName = ruleset.GetName().Name;
|
||||||
|
|
||||||
|
if (rulesetName == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || rulesetName.Contains(@"Tests"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addRuleset(ruleset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadUserRulesets(Storage rulesetStorage)
|
||||||
|
{
|
||||||
|
var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
|
foreach (var ruleset in rulesets.Where(f => !f.Contains(@"Tests")))
|
||||||
|
loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromDisk()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
|
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
|
||||||
|
loadRulesetFromFile(file);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRulesetFromFile(string file)
|
||||||
|
{
|
||||||
|
var filename = Path.GetFileNameWithoutExtension(file);
|
||||||
|
|
||||||
|
if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addRuleset(Assembly.LoadFrom(file));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Failed to load ruleset {filename}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRuleset(Assembly assembly)
|
||||||
|
{
|
||||||
|
if (loadedAssemblies.ContainsKey(assembly))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799).
|
||||||
|
// as a failsafe, also compare by FullName.
|
||||||
|
if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
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 -= resolveRulesetDependencyAssembly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
||||||
{
|
{
|
||||||
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
||||||
((IMultiplayerClient)this).UserJoined(roomUser);
|
|
||||||
|
addUser(roomUser);
|
||||||
|
|
||||||
if (markAsPlaying)
|
if (markAsPlaying)
|
||||||
PlayingUserIds.Add(user.Id);
|
PlayingUserIds.Add(user.Id);
|
||||||
@ -61,7 +62,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return roomUser;
|
return roomUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddNullUser() => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
||||||
|
|
||||||
|
private void addUser(MultiplayerRoomUser user)
|
||||||
|
{
|
||||||
|
((IMultiplayerClient)this).UserJoined(user).Wait();
|
||||||
|
|
||||||
|
// We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation.
|
||||||
|
Scheduler.Update();
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveUser(User user)
|
public void RemoveUser(User user)
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.6.0" />
|
<PackageReference Include="Realm" Version="10.6.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1014.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.1014.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1015.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.9.4" />
|
<PackageReference Include="Sentry" Version="3.9.4" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.29.0" />
|
<PackageReference Include="SharpCompress" Version="0.29.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1014.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1014.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1015.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
Reference in New Issue
Block a user