mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'master' into fix-skin-sample-lookup
This commit is contained in:
commit
de0e51a81d
@ -28,53 +28,56 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
case HUDSkinComponents.ComboCounter:
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
return providesComboCounter ? Drawable.Empty() : null;
|
||||
if (providesComboCounter)
|
||||
return Drawable.Empty();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
||||
return null;
|
||||
|
||||
switch (catchSkinComponent.Component)
|
||||
if (component is CatchSkinComponent catchSkinComponent)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
private Drawable getResult(HitResult result)
|
||||
|
@ -34,90 +34,90 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
if (!(component is OsuSkinComponent osuComponent))
|
||||
return null;
|
||||
|
||||
switch (osuComponent.Component)
|
||||
if (component is OsuSkinComponent osuComponent)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
|
||||
if (sliderBallContent != null)
|
||||
return new LegacySliderBall(sliderBallContent);
|
||||
if (sliderBallContent != null)
|
||||
return new LegacySliderBall(sliderBallContent);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (Source.GetTexture("cursor") != null)
|
||||
return new LegacyCursor();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (Source.GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
};
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||
return null;
|
||||
|
||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.Cursor:
|
||||
if (Source.GetTexture("cursor") != null)
|
||||
return new LegacyCursor();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorTrail:
|
||||
if (Source.GetTexture("cursortrail") != null)
|
||||
return new LegacyCursorTrail();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
};
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||
|
||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -38,98 +38,98 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
}
|
||||
|
||||
if (!(component is TaikoSkinComponent taikoComponent))
|
||||
return null;
|
||||
|
||||
switch (taikoComponent.Component)
|
||||
if (component is TaikoSkinComponent taikoComponent)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
switch (taikoComponent.Component)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return Drawable.Empty();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return Drawable.Empty();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
|
||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||
}
|
||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||
// suppress the default kiai explosion if the skin brings its own sprites.
|
||||
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||
// suppress the default kiai explosion if the skin brings its own sprites.
|
||||
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
}
|
||||
}
|
||||
|
||||
return Source.GetDrawableComponent(component);
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -70,15 +72,46 @@ namespace osu.Game.Tests.Gameplay
|
||||
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLifetimeUpdatedOnDefaultApplied()
|
||||
{
|
||||
TestLifetimeEntry entry = null;
|
||||
AddStep("Create entry", () => entry = new TestLifetimeEntry(new HitObject()) { LifetimeStart = 1 });
|
||||
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||
AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET);
|
||||
|
||||
TestDrawableHitObject dho = null;
|
||||
AddStep("Create DHO", () =>
|
||||
{
|
||||
dho = new TestDrawableHitObject(null);
|
||||
dho.Apply(entry);
|
||||
Child = dho;
|
||||
dho.SetLifetimeStartOnApply = true;
|
||||
});
|
||||
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||
AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY);
|
||||
}
|
||||
|
||||
private class TestDrawableHitObject : DrawableHitObject
|
||||
{
|
||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||
public const double LIFETIME_ON_APPLY = 222;
|
||||
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
||||
|
||||
public bool SetLifetimeStartOnApply;
|
||||
|
||||
public TestDrawableHitObject(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
if (SetLifetimeStartOnApply)
|
||||
LifetimeStart = LIFETIME_ON_APPLY;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -37,6 +38,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Test]
|
||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||
{
|
||||
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||
}
|
||||
|
||||
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||
{
|
||||
CreateTest(() =>
|
||||
|
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Wiki;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneWikiMainPage : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||
|
||||
public TestSceneWikiMainPage()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = overlayColour.Background5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(20),
|
||||
Child = new WikiMainPage
|
||||
{
|
||||
Markdown = main_page_markdown
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
|
||||
private const string main_page_markdown =
|
||||
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
|
||||
}
|
||||
}
|
@ -358,7 +358,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||
|
||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
||||
|
||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
|
||||
|
||||
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap);
|
||||
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -2,7 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
@ -11,13 +13,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[TestFixture]
|
||||
public class TestSceneDialogOverlay : OsuTestScene
|
||||
{
|
||||
public TestSceneDialogOverlay()
|
||||
private DialogOverlay overlay;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
DialogOverlay overlay;
|
||||
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
|
||||
}
|
||||
|
||||
Add(overlay = new DialogOverlay());
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
TestPopupDialog dialog = null;
|
||||
|
||||
AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
|
||||
AddStep("dialog #1", () => overlay.Push(dialog = new TestPopupDialog
|
||||
{
|
||||
Icon = FontAwesome.Regular.TrashAlt,
|
||||
HeaderText = @"Confirm deletion of",
|
||||
@ -37,7 +46,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
}));
|
||||
|
||||
AddStep("dialog #2", () => overlay.Push(new TestPopupDialog
|
||||
AddAssert("first dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||
|
||||
AddStep("dialog #2", () => overlay.Push(dialog = new TestPopupDialog
|
||||
{
|
||||
Icon = FontAwesome.Solid.Cog,
|
||||
HeaderText = @"What do you want to do with",
|
||||
@ -70,6 +81,42 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
AddAssert("second dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDismissBeforePush()
|
||||
{
|
||||
AddStep("dismissed dialog push", () =>
|
||||
{
|
||||
overlay.Push(new TestPopupDialog
|
||||
{
|
||||
State = { Value = Visibility.Hidden }
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDismissBeforePushViaButtonPress()
|
||||
{
|
||||
AddStep("dismissed dialog push", () =>
|
||||
{
|
||||
TestPopupDialog dialog;
|
||||
overlay.Push(dialog = new TestPopupDialog
|
||||
{
|
||||
Buttons = new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogOkButton { Text = @"OK" },
|
||||
},
|
||||
});
|
||||
|
||||
dialog.PerformOkAction();
|
||||
});
|
||||
|
||||
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||
}
|
||||
|
||||
private class TestPopupDialog : PopupDialog
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
@ -244,6 +245,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
var setInfo = info.BeatmapSet;
|
||||
|
||||
Debug.Assert(setInfo.Files != null);
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
@ -290,7 +293,9 @@ namespace osu.Game.Beatmaps
|
||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
||||
return DefaultBeatmap;
|
||||
|
||||
if (beatmapInfo.BeatmapSet.Files == null)
|
||||
// force a re-query if files are not in a state which looks like the model has
|
||||
// full database information present.
|
||||
if (beatmapInfo.BeatmapSet.Files == null || beatmapInfo.BeatmapSet.Files.Count == 0)
|
||||
{
|
||||
var info = beatmapInfo;
|
||||
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
||||
|
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Input;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Input
|
||||
{
|
||||
public class OsuUserInputManager : UserInputManager
|
||||
{
|
||||
internal OsuUserInputManager()
|
||||
{
|
||||
}
|
||||
|
||||
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MouseButton.Right:
|
||||
return new RightMouseManager(button);
|
||||
}
|
||||
|
||||
return base.CreateButtonEventManagerFor(button);
|
||||
}
|
||||
|
||||
private class RightMouseManager : MouseButtonEventManager
|
||||
{
|
||||
public RightMouseManager(MouseButton button)
|
||||
: base(button)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
|
||||
public override bool EnableClick => false;
|
||||
public override bool ChangeFocusOnClick => false;
|
||||
}
|
||||
}
|
||||
}
|
@ -235,6 +235,9 @@ namespace osu.Game.Online.Spectator
|
||||
{
|
||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||
|
||||
if (!IsPlaying)
|
||||
return;
|
||||
|
||||
if (frame is IConvertibleReplayFrame convertible)
|
||||
pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap));
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
@ -41,7 +40,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||
|
||||
namespace osu.Game
|
||||
@ -51,78 +49,19 @@ namespace osu.Game
|
||||
/// Unlike <see cref="OsuGame"/>, this class will not load any kind of UI, allowing it to be used
|
||||
/// for provide dependencies to test cases without interfering with them.
|
||||
/// </summary>
|
||||
public class OsuGameBase : Framework.Game, ICanAcceptFiles
|
||||
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles
|
||||
{
|
||||
public const string CLIENT_STREAM_NAME = "lazer";
|
||||
public const string CLIENT_STREAM_NAME = @"lazer";
|
||||
|
||||
public const int SAMPLE_CONCURRENCY = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
||||
/// </summary>
|
||||
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
|
||||
|
||||
public bool UseDevelopmentServer { get; }
|
||||
|
||||
protected OsuConfigManager LocalConfig;
|
||||
|
||||
protected SessionStatics SessionStatics { get; private set; }
|
||||
|
||||
protected BeatmapManager BeatmapManager;
|
||||
|
||||
protected ScoreManager ScoreManager;
|
||||
|
||||
protected BeatmapDifficultyCache DifficultyCache;
|
||||
|
||||
protected UserLookupCache UserCache;
|
||||
|
||||
protected SkinManager SkinManager;
|
||||
|
||||
protected RulesetStore RulesetStore;
|
||||
|
||||
protected FileStore FileStore;
|
||||
|
||||
protected KeyBindingStore KeyBindingStore;
|
||||
|
||||
protected SettingsStore SettingsStore;
|
||||
|
||||
protected RulesetConfigCache RulesetConfigCache;
|
||||
|
||||
protected IAPIProvider API;
|
||||
|
||||
private SpectatorClient spectatorClient;
|
||||
private MultiplayerClient multiplayerClient;
|
||||
|
||||
protected MenuCursorContainer MenuCursorContainer;
|
||||
|
||||
protected MusicController MusicController;
|
||||
|
||||
private Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
protected Storage Storage { get; set; }
|
||||
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<RulesetInfo>))]
|
||||
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// The current mod selection for the local user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy.
|
||||
/// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection.
|
||||
/// As such, all settings should be finalised before adding a mod to this collection.
|
||||
/// </remarks>
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
/// <summary>
|
||||
/// Mods available for the current <see cref="Ruleset"/>.
|
||||
/// </summary>
|
||||
public readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> AvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||
|
||||
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
|
||||
|
||||
private Bindable<bool> fpsDisplayVisible;
|
||||
|
||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||
|
||||
/// <summary>
|
||||
@ -144,30 +83,83 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
|
||||
protected OsuConfigManager LocalConfig { get; private set; }
|
||||
|
||||
protected SessionStatics SessionStatics { get; private set; }
|
||||
|
||||
protected BeatmapManager BeatmapManager { get; private set; }
|
||||
|
||||
protected ScoreManager ScoreManager { get; private set; }
|
||||
|
||||
protected SkinManager SkinManager { get; private set; }
|
||||
|
||||
protected RulesetStore RulesetStore { get; private set; }
|
||||
|
||||
protected KeyBindingStore KeyBindingStore { get; private set; }
|
||||
|
||||
protected MenuCursorContainer MenuCursorContainer { get; private set; }
|
||||
|
||||
protected MusicController MusicController { get; private set; }
|
||||
|
||||
protected IAPIProvider API { get; set; }
|
||||
|
||||
protected Storage Storage { get; set; }
|
||||
|
||||
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
|
||||
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<RulesetInfo>))]
|
||||
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// The current mod selection for the local user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy.
|
||||
/// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection.
|
||||
/// As such, all settings should be finalised before adding a mod to this collection.
|
||||
/// </remarks>
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
/// <summary>
|
||||
/// Mods available for the current <see cref="Ruleset"/>.
|
||||
/// </summary>
|
||||
public readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> AvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||
|
||||
private BeatmapDifficultyCache difficultyCache;
|
||||
|
||||
private UserLookupCache userCache;
|
||||
|
||||
private FileStore fileStore;
|
||||
|
||||
private SettingsStore settingsStore;
|
||||
|
||||
private RulesetConfigCache rulesetConfigCache;
|
||||
|
||||
private SpectatorClient spectatorClient;
|
||||
|
||||
private MultiplayerClient multiplayerClient;
|
||||
|
||||
private DatabaseContextFactory contextFactory;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private Container content;
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
private Bindable<bool> fpsDisplayVisible;
|
||||
|
||||
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(GLOBAL_TRACK_VOLUME_ADJUST);
|
||||
|
||||
public OsuGameBase()
|
||||
{
|
||||
UseDevelopmentServer = DebugUtils.IsDebugBuild;
|
||||
Name = @"osu!lazer";
|
||||
}
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
private DatabaseContextFactory contextFactory;
|
||||
|
||||
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
||||
|
||||
protected virtual BatteryInfo CreateBatteryInfo() => null;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
||||
/// </summary>
|
||||
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
|
||||
|
||||
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(GLOBAL_TRACK_VOLUME_ADJUST);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -246,10 +238,10 @@ namespace osu.Game
|
||||
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
||||
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
|
||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
||||
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyCache, LocalConfig));
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true));
|
||||
|
||||
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
||||
@ -273,19 +265,19 @@ namespace osu.Game
|
||||
ScoreManager.Undelete(getBeatmapScores(item), true);
|
||||
});
|
||||
|
||||
dependencies.Cache(DifficultyCache = new BeatmapDifficultyCache());
|
||||
AddInternal(DifficultyCache);
|
||||
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
||||
AddInternal(difficultyCache);
|
||||
|
||||
dependencies.Cache(UserCache = new UserLookupCache());
|
||||
AddInternal(UserCache);
|
||||
dependencies.Cache(userCache = new UserLookupCache());
|
||||
AddInternal(userCache);
|
||||
|
||||
var scorePerformanceManager = new ScorePerformanceCache();
|
||||
dependencies.Cache(scorePerformanceManager);
|
||||
AddInternal(scorePerformanceManager);
|
||||
|
||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||
dependencies.Cache(settingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore));
|
||||
|
||||
var powerStatus = CreateBatteryInfo();
|
||||
if (powerStatus != null)
|
||||
@ -308,7 +300,7 @@ namespace osu.Game
|
||||
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
||||
dependencies.CacheAs(Beatmap);
|
||||
|
||||
FileStore.Cleanup();
|
||||
fileStore.Cleanup();
|
||||
|
||||
// add api components to hierarchy.
|
||||
if (API is APIAccess apiAccess)
|
||||
@ -316,7 +308,7 @@ namespace osu.Game
|
||||
AddInternal(spectatorClient);
|
||||
AddInternal(multiplayerClient);
|
||||
|
||||
AddInternal(RulesetConfigCache);
|
||||
AddInternal(rulesetConfigCache);
|
||||
|
||||
GlobalActionContainer globalBindings;
|
||||
|
||||
@ -344,24 +336,6 @@ namespace osu.Game
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
}
|
||||
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
||||
{
|
||||
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
|
||||
|
||||
if (r.NewValue?.Available == true)
|
||||
{
|
||||
foreach (ModType type in Enum.GetValues(typeof(ModType)))
|
||||
dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
|
||||
}
|
||||
|
||||
if (!SelectedMods.Disabled)
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
|
||||
AvailableMods.Value = dict;
|
||||
}
|
||||
|
||||
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -375,28 +349,8 @@ namespace osu.Game
|
||||
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
||||
}
|
||||
|
||||
private void runMigrations()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = contextFactory.GetForWrite(false))
|
||||
db.Context.Migrate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database);
|
||||
|
||||
// if we failed, let's delete the database and start fresh.
|
||||
// todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.
|
||||
contextFactory.ResetDatabase();
|
||||
|
||||
Logger.Log("Database purged successfully.", LoggingTarget.Database);
|
||||
|
||||
// only run once more, then hard bail.
|
||||
using (var db = contextFactory.GetForWrite(false))
|
||||
db.Context.Migrate();
|
||||
}
|
||||
}
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
public override void SetHost(GameHost host)
|
||||
{
|
||||
@ -422,51 +376,59 @@ namespace osu.Game
|
||||
Scheduler.AddDelayed(GracefullyExit, 2000);
|
||||
}
|
||||
|
||||
public void Migrate(string path)
|
||||
{
|
||||
contextFactory.FlushConnections();
|
||||
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||
}
|
||||
|
||||
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
||||
|
||||
protected virtual BatteryInfo CreateBatteryInfo() => null;
|
||||
|
||||
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
|
||||
|
||||
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
|
||||
|
||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||
|
||||
/// <summary>
|
||||
/// Register a global handler for file imports. Most recently registered will have precedence.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to register.</param>
|
||||
public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a global handler for file imports.
|
||||
/// </summary>
|
||||
/// <param name="handler">The previously registered handler.</param>
|
||||
public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler);
|
||||
|
||||
public async Task Import(params string[] paths)
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
||||
{
|
||||
if (paths.Length == 0)
|
||||
return;
|
||||
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
|
||||
|
||||
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
||||
|
||||
foreach (var groups in filesPerExtension)
|
||||
if (r.NewValue?.Available == true)
|
||||
{
|
||||
foreach (var importer in fileImporters)
|
||||
{
|
||||
if (importer.HandledExtensions.Contains(groups.Key))
|
||||
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
foreach (ModType type in Enum.GetValues(typeof(ModType)))
|
||||
dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
|
||||
}
|
||||
|
||||
if (!SelectedMods.Disabled)
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
|
||||
AvailableMods.Value = dict;
|
||||
}
|
||||
|
||||
private void runMigrations()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = contextFactory.GetForWrite(false))
|
||||
db.Context.Migrate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database);
|
||||
|
||||
// if we failed, let's delete the database and start fresh.
|
||||
// todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.
|
||||
contextFactory.ResetDatabase();
|
||||
|
||||
Logger.Log("Database purged successfully.", LoggingTarget.Database);
|
||||
|
||||
// only run once more, then hard bail.
|
||||
using (var db = contextFactory.GetForWrite(false))
|
||||
db.Context.Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task Import(params ImportTask[] tasks)
|
||||
{
|
||||
var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant());
|
||||
await Task.WhenAll(tasksPerExtension.Select(taskGroup =>
|
||||
{
|
||||
var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key));
|
||||
return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask;
|
||||
})).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public IEnumerable<string> HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
@ -477,37 +439,5 @@ namespace osu.Game
|
||||
|
||||
contextFactory.FlushConnections();
|
||||
}
|
||||
|
||||
private class OsuUserInputManager : UserInputManager
|
||||
{
|
||||
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MouseButton.Right:
|
||||
return new RightMouseManager(button);
|
||||
}
|
||||
|
||||
return base.CreateButtonEventManagerFor(button);
|
||||
}
|
||||
|
||||
private class RightMouseManager : MouseButtonEventManager
|
||||
{
|
||||
public RightMouseManager(MouseButton button)
|
||||
: base(button)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
|
||||
public override bool EnableClick => false;
|
||||
public override bool ChangeFocusOnClick => false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Migrate(string path)
|
||||
{
|
||||
contextFactory.FlushConnections();
|
||||
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
57
osu.Game/OsuGameBase_Importing.cs
Normal file
57
osu.Game/OsuGameBase_Importing.cs
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
public partial class OsuGameBase
|
||||
{
|
||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||
|
||||
/// <summary>
|
||||
/// Register a global handler for file imports. Most recently registered will have precedence.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to register.</param>
|
||||
public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a global handler for file imports.
|
||||
/// </summary>
|
||||
/// <param name="handler">The previously registered handler.</param>
|
||||
public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler);
|
||||
|
||||
public async Task Import(params string[] paths)
|
||||
{
|
||||
if (paths.Length == 0)
|
||||
return;
|
||||
|
||||
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
||||
|
||||
foreach (var groups in filesPerExtension)
|
||||
{
|
||||
foreach (var importer in fileImporters)
|
||||
{
|
||||
if (importer.HandledExtensions.Contains(groups.Key))
|
||||
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task Import(params ImportTask[] tasks)
|
||||
{
|
||||
var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant());
|
||||
await Task.WhenAll(tasksPerExtension.Select(taskGroup =>
|
||||
{
|
||||
var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key));
|
||||
return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask;
|
||||
})).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public IEnumerable<string> HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
|
||||
}
|
||||
}
|
@ -95,6 +95,10 @@ namespace osu.Game.Overlays.Dialog
|
||||
}
|
||||
}
|
||||
|
||||
// We always want dialogs to show their appear animation, so we request they start hidden.
|
||||
// Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete().
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
protected PopupDialog()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -205,8 +209,17 @@ namespace osu.Game.Overlays.Dialog
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// It's important we start in a visible state so our state fires on hide, even before load.
|
||||
// This is used by the DialogOverlay to know when the dialog was dismissed.
|
||||
Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Programmatically clicks the first <see cref="PopupDialogOkButton"/>.
|
||||
/// </summary>
|
||||
public void PerformOkAction() => Buttons.OfType<PopupDialogOkButton>().First().Click();
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
@ -35,15 +35,16 @@ namespace osu.Game.Overlays
|
||||
|
||||
public void Push(PopupDialog dialog)
|
||||
{
|
||||
if (dialog == CurrentDialog) return;
|
||||
if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return;
|
||||
|
||||
// if any existing dialog is being displayed, dismiss it before showing a new one.
|
||||
CurrentDialog?.Hide();
|
||||
|
||||
CurrentDialog = dialog;
|
||||
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||
|
||||
dialogContainer.Add(CurrentDialog);
|
||||
|
||||
CurrentDialog.Show();
|
||||
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||
Show();
|
||||
}
|
||||
|
||||
|
104
osu.Game/Overlays/Wiki/WikiMainPage.cs
Normal file
104
osu.Game/Overlays/Wiki/WikiMainPage.cs
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using HtmlAgilityPack;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki
|
||||
{
|
||||
public class WikiMainPage : FillFlowContainer
|
||||
{
|
||||
public string Markdown;
|
||||
|
||||
public WikiMainPage()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var html = new HtmlDocument();
|
||||
html.LoadHtml(Markdown);
|
||||
|
||||
var panels = createPanels(html).ToArray();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
createBlurb(html),
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.AutoSize), panels.Length).ToArray(),
|
||||
Content = panels,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private Container createBlurb(HtmlDocument html)
|
||||
{
|
||||
var blurbNode = html.DocumentNode.SelectSingleNode("//div[contains(@class, 'wiki-main-page__blurb')]");
|
||||
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 30,
|
||||
},
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Text = blurbNode.InnerText,
|
||||
Font = OsuFont.GetFont(size: 12),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<Drawable[]> createPanels(HtmlDocument html)
|
||||
{
|
||||
var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray();
|
||||
|
||||
Debug.Assert(panelsNode.Length > 1);
|
||||
|
||||
var i = 0;
|
||||
|
||||
while (i < panelsNode.Length)
|
||||
{
|
||||
var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full");
|
||||
|
||||
if (isFullWidth)
|
||||
{
|
||||
yield return new Drawable[]
|
||||
{
|
||||
new WikiPanelContainer(panelsNode[i++].InnerText, true)
|
||||
{
|
||||
// This is required to fill up the space of "null" drawable below.
|
||||
Width = 2,
|
||||
},
|
||||
null,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new Drawable[]
|
||||
{
|
||||
new WikiPanelContainer(panelsNode[i++].InnerText),
|
||||
i < panelsNode.Length ? new WikiPanelContainer(panelsNode[i++].InnerText) : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
125
osu.Game/Overlays/Wiki/WikiPanelContainer.cs
Normal file
125
osu.Game/Overlays/Wiki/WikiPanelContainer.cs
Normal file
@ -0,0 +1,125 @@
|
||||
// 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 Markdig.Syntax;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
using osu.Game.Overlays.Wiki.Markdown;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki
|
||||
{
|
||||
public class WikiPanelContainer : Container
|
||||
{
|
||||
private WikiPanelMarkdownContainer panelContainer;
|
||||
|
||||
private readonly string text;
|
||||
|
||||
private readonly bool isFullWidth;
|
||||
|
||||
public WikiPanelContainer(string text, bool isFullWidth = false)
|
||||
{
|
||||
this.text = text;
|
||||
this.isFullWidth = isFullWidth;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Padding = new MarginPadding(3);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 4,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(25),
|
||||
Offset = new Vector2(0, 1),
|
||||
Radius = 3,
|
||||
},
|
||||
Child = new Box
|
||||
{
|
||||
Colour = colourProvider.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
panelContainer = new WikiPanelMarkdownContainer(isFullWidth)
|
||||
{
|
||||
Text = text,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Height = Math.Max(panelContainer.Height, Parent.DrawHeight);
|
||||
}
|
||||
|
||||
private class WikiPanelMarkdownContainer : WikiMarkdownContainer
|
||||
{
|
||||
private readonly bool isFullWidth;
|
||||
|
||||
public WikiPanelMarkdownContainer(bool isFullWidth)
|
||||
{
|
||||
this.isFullWidth = isFullWidth;
|
||||
|
||||
LineSpacing = 0;
|
||||
DocumentPadding = new MarginPadding(30);
|
||||
DocumentMargin = new MarginPadding(0);
|
||||
}
|
||||
|
||||
public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre);
|
||||
|
||||
protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level)
|
||||
=> base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 });
|
||||
|
||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock)
|
||||
{
|
||||
IsFullWidth = isFullWidth,
|
||||
};
|
||||
}
|
||||
|
||||
private class WikiPanelHeading : OsuMarkdownHeading
|
||||
{
|
||||
public bool IsFullWidth;
|
||||
|
||||
public WikiPanelHeading(HeadingBlock headingBlock)
|
||||
: base(headingBlock)
|
||||
{
|
||||
Margin = new MarginPadding { Bottom = 40 };
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f =>
|
||||
{
|
||||
f.Anchor = Anchor.TopCentre;
|
||||
f.Origin = Anchor.TopCentre;
|
||||
f.TextAnchor = Anchor.TopCentre;
|
||||
});
|
||||
|
||||
protected override FontWeight GetFontWeightByLevel(int level) => FontWeight.Light;
|
||||
|
||||
protected override float GetFontSizeByLevel(int level) => base.GetFontSizeByLevel(IsFullWidth ? level : 3);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
@ -445,9 +444,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Apply (generally fade-in) transforms leading into the <see cref="HitObject"/> start time.
|
||||
/// The local drawable hierarchy is recursively delayed to <see cref="LifetimeEntry.LifetimeStart"/> for convenience.
|
||||
///
|
||||
/// By default this will fade in the object from zero with no duration.
|
||||
/// By default, this will fade in the object from zero with no duration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is called once before every <see cref="UpdateStateTransforms"/>. This is to ensure a good state in the case
|
||||
@ -623,17 +620,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
/// <summary>
|
||||
/// A safe offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
||||
/// An offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
||||
/// By default, <see cref="DrawableHitObject"/>s are assumed to display their contents within 10 seconds prior to the start time of <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used as an optimisation to delay the initial update of this <see cref="DrawableHitObject"/> and may be tuned more aggressively if required.
|
||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="UpdateInitialTransforms"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="LoadComplete"/>, for example).
|
||||
/// <para>
|
||||
/// Only has an effect if this <see cref="DrawableHitObject"/> is not being pooled.
|
||||
/// For pooled <see cref="DrawableHitObject"/>s, use <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/> instead.
|
||||
/// </para>
|
||||
/// The initial transformation (<see cref="UpdateInitialTransforms"/>) starts at this offset before the start time of <see cref="HitObject"/>.
|
||||
/// </remarks>
|
||||
protected virtual double InitialLifetimeOffset => 10000;
|
||||
|
||||
|
@ -35,7 +35,11 @@ namespace osu.Game.Rulesets.Objects
|
||||
HitObject = hitObject;
|
||||
|
||||
startTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||
startTimeBindable.BindValueChanged(onStartTimeChanged, true);
|
||||
startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true);
|
||||
|
||||
// Subscribe to this event before the DrawableHitObject so that the local callback is invoked before the entry is re-applied as a result of DefaultsApplied.
|
||||
// This way, the DrawableHitObject can use OnApply() to overwrite the LifetimeStart that was set inside setInitialLifetime().
|
||||
HitObject.DefaultsApplied += _ => setInitialLifetime();
|
||||
}
|
||||
|
||||
// The lifetime, as set by the hitobject.
|
||||
@ -82,15 +86,14 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// By default, <see cref="HitObject"/>s are assumed to display their contents within 10 seconds prior to their start time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used as an optimisation to delay the initial update of the <see cref="HitObject"/> and may be tuned more aggressively if required.
|
||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="DrawableHitObject.UpdateInitialTransforms"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="DrawableHitObject.LoadComplete"/>, for example).
|
||||
/// This is only used as an optimisation to delay the initial application of the <see cref="HitObject"/> to a <see cref="DrawableHitObject"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set on the hit object application, for further optimisation.
|
||||
/// </remarks>
|
||||
protected virtual double InitialLifetimeOffset => 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Resets <see cref="LifetimeEntry.LifetimeStart"/> according to the change in start time of the <see cref="HitObject"/>.
|
||||
/// Set <see cref="LifetimeEntry.LifetimeStart"/> using <see cref="InitialLifetimeOffset"/>.
|
||||
/// </summary>
|
||||
private void onStartTimeChanged(ValueChangedEvent<double> startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||
private void setInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||
}
|
||||
}
|
||||
|
@ -473,25 +473,23 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
if (!exitConfirmed)
|
||||
{
|
||||
// if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save.
|
||||
if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog)
|
||||
// dialog overlay may not be available in visual tests.
|
||||
if (dialogOverlay == null)
|
||||
{
|
||||
confirmExit();
|
||||
return base.OnExiting(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the dialog is already displayed, confirm exit with no save.
|
||||
if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog)
|
||||
{
|
||||
saveDialog.PerformOkAction();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNewBeatmap || HasUnsavedChanges)
|
||||
{
|
||||
dialogOverlay?.Push(new PromptForSaveDialog(() =>
|
||||
{
|
||||
confirmExit();
|
||||
this.Exit();
|
||||
}, () =>
|
||||
{
|
||||
confirmExitWithSave();
|
||||
this.Exit();
|
||||
}));
|
||||
|
||||
dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -499,15 +497,23 @@ namespace osu.Game.Screens.Edit
|
||||
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
||||
resetTrack();
|
||||
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||
// To update the game-wide beatmap with any changes, perform a re-fetch on exit.
|
||||
// This is required as the editor makes its local changes via EditorBeatmap
|
||||
// (which are not propagated outwards to a potentially cached WorkingBeatmap).
|
||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||
|
||||
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
||||
Beatmap.Value = refetchedBeatmap;
|
||||
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
||||
private void confirmExitWithSave()
|
||||
{
|
||||
exitConfirmed = true;
|
||||
Save();
|
||||
|
||||
exitConfirmed = true;
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
private void confirmExit()
|
||||
@ -529,6 +535,7 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
|
||||
exitConfirmed = true;
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
private readonly Bindable<string> clipboard = new Bindable<string>();
|
||||
|
@ -1,9 +1,6 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -22,6 +19,8 @@ using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
@ -120,7 +119,7 @@ namespace osu.Game.Screens.Menu
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding { Right = 15, Top = 5 }
|
||||
},
|
||||
exitConfirmOverlay?.CreateProxy() ?? Drawable.Empty()
|
||||
exitConfirmOverlay?.CreateProxy() ?? Empty()
|
||||
});
|
||||
|
||||
buttons.StateChanged += state =>
|
||||
@ -270,15 +269,11 @@ namespace osu.Game.Screens.Menu
|
||||
if (!exitConfirmed && dialogOverlay != null)
|
||||
{
|
||||
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
||||
{
|
||||
exitConfirmed = true;
|
||||
exitDialog.Buttons.First().Click();
|
||||
}
|
||||
exitDialog.PerformOkAction();
|
||||
else
|
||||
{
|
||||
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
buttons.State = ButtonSystemState.Exit;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -23,6 +24,25 @@ namespace osu.Game.Skinning
|
||||
Configuration.AllowDefaultComboColoursFallback = false;
|
||||
}
|
||||
|
||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
if (component is SkinnableTargetComponent targetComponent)
|
||||
{
|
||||
switch (targetComponent.Target)
|
||||
{
|
||||
case SkinnableTarget.MainHUDComponents:
|
||||
// this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
|
||||
// therefore keep the check here until fallback default legacy skin is supported.
|
||||
if (!this.HasFont(LegacyFont.Score))
|
||||
return null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
switch (lookup)
|
||||
@ -51,6 +71,6 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
|
||||
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
|
||||
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
|
||||
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString };
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="DiffPlex" Version="1.7.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
|
||||
<PackageReference Include="Humanizer" Version="2.8.26" />
|
||||
<PackageReference Include="MessagePack" Version="2.2.85" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.5" />
|
||||
|
Loading…
Reference in New Issue
Block a user