mirror of https://github.com/ppy/osu
Merge branch 'master' into fix-editor-difficulty-name-update
This commit is contained in:
commit
3604a762d0
|
@ -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 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
|
|||
new OsuModBarrelRoll(),
|
||||
new OsuModApproachDifferent(),
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
|
@ -25,7 +25,7 @@ public override void SetUpSteps()
|
|||
[Test]
|
||||
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.
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
|
@ -19,28 +23,62 @@ private void createSliderBar(bool hasDescription = false)
|
|||
{
|
||||
AddStep("create component", () =>
|
||||
{
|
||||
LabelledSliderBar<double> component;
|
||||
FillFlowContainer flow;
|
||||
|
||||
Child = new Container
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 500,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = component = new LabelledSliderBar<double>
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Current = new BindableDouble(5)
|
||||
new LabelledSliderBar<double>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
}
|
||||
}
|
||||
Current = new BindableDouble(5)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
},
|
||||
Label = "a sample component",
|
||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
component.Label = "a sample component";
|
||||
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
||||
foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||
{
|
||||
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 @@ public Action<Notification> PostNotification
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the user requests to view the resulting import.
|
||||
/// </summary>
|
||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap difficulty.
|
||||
/// </summary>
|
||||
|
@ -338,5 +333,14 @@ public void Dispose()
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||
|
||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
|
||||
{
|
||||
set => beatmapModelManager.PostImport = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||
/// </summary>
|
||||
/// <typeparam name="TModel">The model 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 TFileModel : class, INamedFileInfo, new()
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace osu.Game.Database
|
|||
/// A class which handles importing of associated models to the game store.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
public interface IModelImporter<TModel> : IPostNotifications
|
||||
public interface IModelImporter<TModel> : IPostNotifications, IPostImports<TModel>
|
||||
where TModel : class
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Statistics;
|
||||
|
@ -18,7 +17,7 @@ namespace osu.Game.Database
|
|||
/// <summary>
|
||||
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
||||
/// </summary>
|
||||
public class RealmContextFactory : Component, IRealmFactory
|
||||
public class RealmContextFactory : IDisposable, IRealmFactory
|
||||
{
|
||||
private readonly Storage storage;
|
||||
|
||||
|
@ -79,10 +78,11 @@ public RealmContextFactory(Storage storage, string filename)
|
|||
/// <returns></returns>
|
||||
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)
|
||||
{
|
||||
if (context?.Refresh() == true)
|
||||
|
@ -92,7 +92,7 @@ protected override void Update()
|
|||
|
||||
public Realm CreateContext()
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (isDisposed)
|
||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||
|
||||
try
|
||||
|
@ -132,7 +132,7 @@ private void onMigration(Migration migration, ulong lastSchemaVersion)
|
|||
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
||||
public IDisposable BlockAllOperations()
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (isDisposed)
|
||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||
|
||||
if (!ThreadSafety.IsUpdateThread)
|
||||
|
@ -176,21 +176,23 @@ public IDisposable BlockAllOperations()
|
|||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
private bool isDisposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (contextLock)
|
||||
{
|
||||
context?.Dispose();
|
||||
}
|
||||
|
||||
if (!IsDisposed)
|
||||
if (!isDisposed)
|
||||
{
|
||||
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
||||
contextCreationLock.Wait();
|
||||
contextCreationLock.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
|
@ -12,63 +13,74 @@
|
|||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
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 EXPANDED_SIZE = 40;
|
||||
public const float HEIGHT = 15;
|
||||
|
||||
public const float EXPANDED_SIZE = 50;
|
||||
|
||||
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 readonly Box fill;
|
||||
private readonly Container main;
|
||||
|
||||
public Nub()
|
||||
{
|
||||
Box fill;
|
||||
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||
|
||||
Size = new Vector2(COLLAPSED_SIZE, 12);
|
||||
|
||||
BorderColour = Color4.White;
|
||||
BorderThickness = border_width;
|
||||
|
||||
Masking = true;
|
||||
|
||||
Children = new[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
fill = new Box
|
||||
main = new CircularContainer
|
||||
{
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = border_width,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
fill = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
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]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.Pink;
|
||||
GlowingAccentColour = colours.PinkLighter;
|
||||
GlowColour = colours.PinkDarker;
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
|
||||
GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
|
||||
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
main.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = GlowColour.Opacity(0),
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 10,
|
||||
Roundness = 8,
|
||||
Radius = 8,
|
||||
Roundness = 5,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||
}
|
||||
|
||||
private bool glowing;
|
||||
|
||||
public bool Glowing
|
||||
|
@ -80,28 +92,17 @@ public bool Glowing
|
|||
|
||||
if (value)
|
||||
{
|
||||
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint);
|
||||
main.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||
main.FadeEdgeEffectTo(0.2f, animate_in_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
FadeEdgeEffectTo(0, animate_out_duration);
|
||||
this.FadeColour(AccentColour, animate_out_duration);
|
||||
main.FadeEdgeEffectTo(0, animate_out_duration, Easing.OutQuint);
|
||||
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>();
|
||||
|
||||
public Bindable<bool> Current
|
||||
|
@ -126,7 +127,7 @@ public Color4 AccentColour
|
|||
{
|
||||
accentColour = value;
|
||||
if (!Glowing)
|
||||
Colour = value;
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +140,7 @@ public Color4 GlowingAccentColour
|
|||
{
|
||||
glowingAccentColour = value;
|
||||
if (Glowing)
|
||||
Colour = value;
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,10 +153,22 @@ public Color4 GlowColour
|
|||
{
|
||||
glowColour = value;
|
||||
|
||||
var effect = EdgeEffect;
|
||||
var effect = main.EdgeEffect;
|
||||
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.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuCheckbox : Checkbox
|
||||
{
|
||||
public Color4 CheckedColor { get; set; } = Color4.Cyan;
|
||||
public Color4 UncheckedColor { get; set; } = Color4.White;
|
||||
public int FadeDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to play sounds when the state changes as a result of user interaction.
|
||||
/// </summary>
|
||||
|
@ -104,14 +99,12 @@ private void load(AudioManager audio)
|
|||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Nub.Glowing = true;
|
||||
Nub.Expanded = true;
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
Nub.Glowing = false;
|
||||
Nub.Expanded = false;
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
|
@ -16,6 +18,7 @@
|
|||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
|
@ -52,34 +55,63 @@ public Color4 AccentColour
|
|||
{
|
||||
accentColour = value;
|
||||
leftBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Colour4 backgroundColour;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
backgroundColour = value;
|
||||
rightBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
public OsuSliderBar()
|
||||
{
|
||||
Height = 12;
|
||||
RangePadding = 20;
|
||||
Height = Nub.HEIGHT;
|
||||
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftBox = new Box
|
||||
new Container
|
||||
{
|
||||
Height = 2,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
Position = new Vector2(2, 0),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
rightBox = new Box
|
||||
{
|
||||
Height = 2,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
Position = new Vector2(-2, 0),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Alpha = 0.5f,
|
||||
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[]
|
||||
{
|
||||
leftBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
rightBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Alpha = 0.5f,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nubContainer = new Container
|
||||
{
|
||||
|
@ -88,7 +120,7 @@ public OsuSliderBar()
|
|||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Expanded = true,
|
||||
Current = { Value = true }
|
||||
},
|
||||
},
|
||||
new HoverClickSounds()
|
||||
|
@ -97,11 +129,12 @@ public OsuSliderBar()
|
|||
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
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()
|
||||
|
@ -119,26 +152,25 @@ protected override void LoadComplete()
|
|||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Nub.Glowing = true;
|
||||
updateGlow();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
Nub.Glowing = false;
|
||||
updateGlow();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
Nub.Current.Value = true;
|
||||
return base.OnMouseDown(e);
|
||||
updateGlow();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
private void updateGlow()
|
||||
{
|
||||
Nub.Current.Value = false;
|
||||
base.OnMouseUp(e);
|
||||
Nub.Glowing = IsHovered || IsDragged;
|
||||
}
|
||||
|
||||
protected override void OnUserChange(T value)
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
|
@ -23,10 +25,10 @@ public override float Height
|
|||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Blue3;
|
||||
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
|
|
@ -374,7 +374,7 @@ async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user)
|
|||
|
||||
UserJoined?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
||||
|
|
|
@ -643,7 +643,7 @@ protected override void LoadComplete()
|
|||
SkinManager.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.PostImport = items => PresentScore(items.First().Value);
|
||||
|
|
|
@ -187,8 +187,6 @@ private void load()
|
|||
|
||||
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
||||
|
||||
AddInternal(realmFactory);
|
||||
|
||||
dependencies.CacheAs(Storage);
|
||||
|
||||
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||
|
@ -529,6 +527,7 @@ protected override void Dispose(bool isDisposing)
|
|||
LocalConfig?.Dispose();
|
||||
|
||||
contextFactory?.FlushConnections();
|
||||
realmFactory?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public class SettingsSlider<TValue, TSlider> : SettingsItem<TValue>
|
|||
{
|
||||
protected override Drawable CreateControl() => new TSlider
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
||||
Margin = new MarginPadding { Vertical = 10 },
|
||||
RelativeSizeAxes = Axes.X
|
||||
};
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
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 Func<BeatmapDifficultyCache> difficulties;
|
||||
|
|
|
@ -14,7 +14,7 @@ private void load(OsuColour colours)
|
|||
{
|
||||
Nub.AccentColour = colours.Yellow;
|
||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||
Nub.GlowColour = colours.YellowDarker;
|
||||
Nub.GlowColour = colours.YellowDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ private void load(OsuColour colours)
|
|||
AccentColour = colours.Yellow;
|
||||
Nub.AccentColour = colours.Yellow;
|
||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||
Nub.GlowColour = colours.YellowDarker;
|
||||
Nub.GlowColour = colours.YellowDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@ public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManag
|
|||
public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
||||
{
|
||||
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
||||
((IMultiplayerClient)this).UserJoined(roomUser);
|
||||
|
||||
addUser(roomUser);
|
||||
|
||||
if (markAsPlaying)
|
||||
PlayingUserIds.Add(user.Id);
|
||||
|
@ -61,7 +62,15 @@ public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
|||
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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue