mirror of
https://github.com/ppy/osu.git
synced 2025-01-06 07:02:54 +08:00
Merge branch 'master' into wiki-article
This commit is contained in:
commit
b192c46316
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.601.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.607.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
AddAssert("correct hit lighting colour", () =>
|
AddAssert("correct hit lighting colour", () =>
|
||||||
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
|
catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == fruitColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
@ -9,21 +8,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CaughtFruit : CaughtObject, IHasFruitState
|
public class CaughtFruit : CaughtObject
|
||||||
{
|
{
|
||||||
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
|
||||||
|
|
||||||
public CaughtFruit()
|
public CaughtFruit()
|
||||||
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CopyStateFrom(IHasCatchObjectState objectState)
|
|
||||||
{
|
|
||||||
base.CopyStateFrom(objectState);
|
|
||||||
|
|
||||||
var fruitState = (IHasFruitState)objectState;
|
|
||||||
VisualRepresentation.Value = fruitState.VisualRepresentation.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
public PalpableCatchHitObject HitObject { get; private set; }
|
public PalpableCatchHitObject HitObject { get; private set; }
|
||||||
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
|
||||||
public Vector2 DisplaySize => Size * Scale;
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Rotation = objectState.DisplayRotation;
|
Rotation = objectState.DisplayRotation;
|
||||||
AccentColour.Value = objectState.AccentColour.Value;
|
AccentColour.Value = objectState.AccentColour.Value;
|
||||||
HyperDash.Value = objectState.HyperDash.Value;
|
HyperDash.Value = objectState.HyperDash.Value;
|
||||||
|
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
protected override void FreeAfterUse()
|
||||||
|
@ -3,17 +3,14 @@
|
|||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
|
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
|
||||||
|
|
||||||
public DrawableFruit()
|
public DrawableFruit()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -27,11 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
IndexInBeatmap.BindValueChanged(change =>
|
|
||||||
{
|
|
||||||
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
ScalingContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
||||||
_ => new FruitPiece());
|
_ => new FruitPiece());
|
||||||
@ -44,12 +36,4 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FruitVisualRepresentation
|
|
||||||
{
|
|
||||||
Pear,
|
|
||||||
Grape,
|
|
||||||
Pineapple,
|
|
||||||
Raspberry,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
Bindable<bool> HyperDash { get; }
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
|
Bindable<int> IndexInBeatmap { get; }
|
||||||
|
|
||||||
Vector2 DisplaySize { get; }
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
float DisplayRotation { get; }
|
float DisplayRotation { get; }
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
// 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.Bindables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a visual state of a <see cref="Fruit"/>.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasFruitState : IHasCatchObjectState
|
|
||||||
{
|
|
||||||
Bindable<FruitVisualRepresentation> VisualRepresentation { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public class Fruit : PalpableCatchHitObject
|
public class Fruit : PalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new CatchJudgement();
|
public override Judgement CreateJudgement() => new CatchJudgement();
|
||||||
|
|
||||||
|
public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
Normal file
13
osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
public enum FruitVisualRepresentation
|
||||||
|
{
|
||||||
|
Pear,
|
||||||
|
Grape,
|
||||||
|
Pineapple,
|
||||||
|
Raspberry,
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IHasCatchObjectState ObjectState { get; private set; }
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
@ -37,6 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
|
|
||||||
AccentColour.BindTo(ObjectState.AccentColour);
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
HyperDash.BindTo(ObjectState.HyperDash);
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
|
IndexInBeatmap.BindTo(ObjectState.IndexInBeatmap);
|
||||||
|
|
||||||
HyperDash.BindValueChanged(hyper =>
|
HyperDash.BindValueChanged(hyper =>
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
{
|
{
|
||||||
@ -39,8 +39,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruitState = (IHasFruitState)ObjectState;
|
IndexInBeatmap.BindValueChanged(index =>
|
||||||
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
{
|
||||||
|
VisualRepresentation.Value = Fruit.GetVisualRepresentation(index.NewValue);
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
public class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
@ -13,12 +13,13 @@ using osu.Game.Skinning;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public abstract class LegacyCatchHitObjectPiece : PoolableDrawable
|
public abstract class LegacyCatchHitObjectPiece : PoolableDrawable
|
||||||
{
|
{
|
||||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
||||||
|
|
||||||
private readonly Sprite colouredSprite;
|
private readonly Sprite colouredSprite;
|
||||||
private readonly Sprite overlaySprite;
|
private readonly Sprite overlaySprite;
|
||||||
@ -64,6 +65,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
|
|
||||||
AccentColour.BindTo(ObjectState.AccentColour);
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
HyperDash.BindTo(ObjectState.HyperDash);
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
|
IndexInBeatmap.BindTo(ObjectState.IndexInBeatmap);
|
||||||
|
|
||||||
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
||||||
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
@ -4,7 +4,7 @@
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
public class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
@ -1,23 +1,20 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
internal class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
internal class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruitState = (IHasFruitState)ObjectState;
|
IndexInBeatmap.BindValueChanged(index =>
|
||||||
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
{
|
||||||
|
setTexture(Fruit.GetVisualRepresentation(index.NewValue));
|
||||||
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTexture(FruitVisualRepresentation visualRepresentation)
|
private void setTexture(FruitVisualRepresentation visualRepresentation)
|
||||||
|
@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
private Bindable<bool> hitLighting;
|
private Bindable<bool> hitLighting;
|
||||||
|
|
||||||
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
private readonly HitExplosionContainer hitExplosionContainer;
|
||||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
|
||||||
|
|
||||||
private readonly DrawablePool<CaughtFruit> caughtFruitPool;
|
private readonly DrawablePool<CaughtFruit> caughtFruitPool;
|
||||||
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
||||||
@ -148,7 +147,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
|
||||||
caughtFruitPool = new DrawablePool<CaughtFruit>(50),
|
caughtFruitPool = new DrawablePool<CaughtFruit>(50),
|
||||||
caughtBananaPool = new DrawablePool<CaughtBanana>(100),
|
caughtBananaPool = new DrawablePool<CaughtBanana>(100),
|
||||||
// less capacity is needed compared to fruit because droplet is not stacked
|
// less capacity is needed compared to fruit because droplet is not stacked
|
||||||
@ -173,7 +171,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
},
|
},
|
||||||
hitExplosionContainer = new Container<HitExplosion>
|
hitExplosionContainer = new HitExplosionContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
@ -297,7 +295,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -508,15 +505,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
|
private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
|
||||||
{
|
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
|
||||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
|
||||||
hitExplosion.HitObject = hitObject;
|
|
||||||
hitExplosion.X = x;
|
|
||||||
hitExplosion.Scale = new Vector2(hitObject.Scale);
|
|
||||||
hitExplosion.ObjectColour = colour;
|
|
||||||
hitExplosionContainer.Add(hitExplosion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
|
@ -5,31 +5,16 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosion : PoolableDrawable
|
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
||||||
{
|
{
|
||||||
private Color4 objectColour;
|
|
||||||
public CatchHitObject HitObject;
|
|
||||||
|
|
||||||
public Color4 ObjectColour
|
|
||||||
{
|
|
||||||
get => objectColour;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (objectColour == value) return;
|
|
||||||
|
|
||||||
objectColour = value;
|
|
||||||
onColourChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CircularContainer largeFaint;
|
private readonly CircularContainer largeFaint;
|
||||||
private readonly CircularContainer smallFaint;
|
private readonly CircularContainer smallFaint;
|
||||||
private readonly CircularContainer directionalGlow1;
|
private readonly CircularContainer directionalGlow1;
|
||||||
@ -83,9 +68,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PrepareForUse()
|
protected override void OnApply(HitExplosionEntry entry)
|
||||||
{
|
{
|
||||||
base.PrepareForUse();
|
X = entry.Position;
|
||||||
|
Scale = new Vector2(entry.Scale);
|
||||||
|
setColour(entry.ObjectColour);
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
applyTransforms(entry.RNGSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTransforms(int randomSeed)
|
||||||
|
{
|
||||||
|
ClearTransforms(true);
|
||||||
|
|
||||||
const double duration = 400;
|
const double duration = 400;
|
||||||
|
|
||||||
@ -96,14 +91,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
.FadeOut(duration * 2);
|
.FadeOut(duration * 2);
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
const float angle_variangle = 15; // should be less than 45
|
||||||
directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
||||||
directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
||||||
|
|
||||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
|
||||||
Expire(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onColourChanged()
|
private void setColour(Color4 objectColour)
|
||||||
{
|
{
|
||||||
const float roundness = 100;
|
const float roundness = 100;
|
||||||
|
|
||||||
|
22
osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
Normal file
22
osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public class HitExplosionContainer : PooledDrawableWithLifetimeContainer<HitExplosionEntry, HitExplosion>
|
||||||
|
{
|
||||||
|
protected override bool RemoveRewoundEntry => true;
|
||||||
|
|
||||||
|
private readonly DrawablePool<HitExplosion> pool;
|
||||||
|
|
||||||
|
public HitExplosionContainer()
|
||||||
|
{
|
||||||
|
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override HitExplosion GetDrawable(HitExplosionEntry entry) => pool.Get(d => d.Apply(entry));
|
||||||
|
}
|
||||||
|
}
|
25
osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
Normal file
25
osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public class HitExplosionEntry : LifetimeEntry
|
||||||
|
{
|
||||||
|
public readonly float Position;
|
||||||
|
public readonly float Scale;
|
||||||
|
public readonly Color4 ObjectColour;
|
||||||
|
public readonly int RNGSeed;
|
||||||
|
|
||||||
|
public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
|
||||||
|
{
|
||||||
|
LifetimeStart = startTime;
|
||||||
|
Position = position;
|
||||||
|
Scale = scale;
|
||||||
|
ObjectColour = objectColour;
|
||||||
|
RNGSeed = rngSeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
Normal file
30
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneHitCircleKiai : TestSceneHitCircle
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
// track needs to be playing for BeatSyncedContainer to work.
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
@ -12,34 +13,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FollowPointConnection : PoolableDrawable
|
public class FollowPointConnection : PoolableDrawableWithLifetime<FollowPointLifetimeEntry>
|
||||||
{
|
{
|
||||||
// Todo: These shouldn't be constants
|
// Todo: These shouldn't be constants
|
||||||
public const int SPACING = 32;
|
public const int SPACING = 32;
|
||||||
public const double PREEMPT = 800;
|
public const double PREEMPT = 800;
|
||||||
|
|
||||||
public FollowPointLifetimeEntry Entry;
|
|
||||||
public DrawablePool<FollowPoint> Pool;
|
public DrawablePool<FollowPoint> Pool;
|
||||||
|
|
||||||
protected override void PrepareForUse()
|
protected override void OnApply(FollowPointLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
base.PrepareForUse();
|
base.OnApply(entry);
|
||||||
|
|
||||||
Entry.Invalidated += onEntryInvalidated;
|
|
||||||
|
|
||||||
|
entry.Invalidated += onEntryInvalidated;
|
||||||
refreshPoints();
|
refreshPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
protected override void OnFree(FollowPointLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
base.FreeAfterUse();
|
base.OnFree(entry);
|
||||||
|
|
||||||
Entry.Invalidated -= onEntryInvalidated;
|
|
||||||
|
|
||||||
|
entry.Invalidated -= onEntryInvalidated;
|
||||||
// Return points to the pool.
|
// Return points to the pool.
|
||||||
ClearInternal(false);
|
ClearInternal(false);
|
||||||
|
|
||||||
Entry = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
||||||
@ -48,8 +44,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
{
|
{
|
||||||
ClearInternal(false);
|
ClearInternal(false);
|
||||||
|
|
||||||
OsuHitObject start = Entry.Start;
|
var entry = Entry;
|
||||||
OsuHitObject end = Entry.End;
|
if (entry?.End == null) return;
|
||||||
|
|
||||||
|
OsuHitObject start = entry.Start;
|
||||||
|
OsuHitObject end = entry.End;
|
||||||
|
|
||||||
double startTime = start.GetEndTime();
|
double startTime = start.GetEndTime();
|
||||||
|
|
||||||
@ -87,14 +86,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
fp.FadeIn(end.TimeFadeIn);
|
fp.FadeIn(end.TimeFadeIn);
|
||||||
fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
|
fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
|
||||||
fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
|
fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
|
||||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);
|
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire();
|
||||||
|
|
||||||
finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
|
finalTransformEndTime = fp.LifetimeEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
|
entry.LifetimeEnd = finalTransformEndTime;
|
||||||
Entry.LifetimeEnd = finalTransformEndTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
@ -11,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
{
|
{
|
||||||
public class FollowPointLifetimeEntry : LifetimeEntry
|
public class FollowPointLifetimeEntry : LifetimeEntry
|
||||||
{
|
{
|
||||||
public event Action Invalidated;
|
public event Action? Invalidated;
|
||||||
public readonly OsuHitObject Start;
|
public readonly OsuHitObject Start;
|
||||||
|
|
||||||
public FollowPointLifetimeEntry(OsuHitObject start)
|
public FollowPointLifetimeEntry(OsuHitObject start)
|
||||||
@ -22,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
bindEvents();
|
bindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuHitObject end;
|
private OsuHitObject? end;
|
||||||
|
|
||||||
public OsuHitObject End
|
public OsuHitObject? End
|
||||||
{
|
{
|
||||||
get => end;
|
get => end;
|
||||||
set
|
set
|
||||||
@ -56,11 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
|
|
||||||
public void UnbindEvents()
|
public void UnbindEvents()
|
||||||
{
|
{
|
||||||
if (Start != null)
|
Start.DefaultsApplied -= onDefaultsApplied;
|
||||||
{
|
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
||||||
Start.DefaultsApplied -= onDefaultsApplied;
|
|
||||||
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (End != null)
|
if (End != null)
|
||||||
{
|
{
|
||||||
|
@ -6,43 +6,32 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Performance;
|
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FollowPointRenderer : CompositeDrawable
|
public class FollowPointRenderer : PooledDrawableWithLifetimeContainer<FollowPointLifetimeEntry, FollowPointConnection>
|
||||||
{
|
{
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public new IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
|
||||||
|
|
||||||
public IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
|
|
||||||
|
|
||||||
private DrawablePool<FollowPointConnection> connectionPool;
|
private DrawablePool<FollowPointConnection> connectionPool;
|
||||||
private DrawablePool<FollowPoint> pointPool;
|
private DrawablePool<FollowPoint> pointPool;
|
||||||
|
|
||||||
private readonly List<FollowPointLifetimeEntry> lifetimeEntries = new List<FollowPointLifetimeEntry>();
|
private readonly List<FollowPointLifetimeEntry> lifetimeEntries = new List<FollowPointLifetimeEntry>();
|
||||||
private readonly Dictionary<LifetimeEntry, FollowPointConnection> connectionsInUse = new Dictionary<LifetimeEntry, FollowPointConnection>();
|
|
||||||
private readonly Dictionary<HitObject, IBindable> startTimeMap = new Dictionary<HitObject, IBindable>();
|
private readonly Dictionary<HitObject, IBindable> startTimeMap = new Dictionary<HitObject, IBindable>();
|
||||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
|
||||||
|
|
||||||
public FollowPointRenderer()
|
|
||||||
{
|
|
||||||
lifetimeManager.EntryBecameAlive += onEntryBecameAlive;
|
|
||||||
lifetimeManager.EntryBecameDead += onEntryBecameDead;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
connectionPool = new DrawablePoolNoLifetime<FollowPointConnection>(1, 200),
|
connectionPool = new DrawablePool<FollowPointConnection>(1, 200),
|
||||||
pointPool = new DrawablePoolNoLifetime<FollowPoint>(50, 1000)
|
pointPool = new DrawablePool<FollowPoint>(50, 1000)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
previousEntry.End = newEntry.Start;
|
previousEntry.End = newEntry.Start;
|
||||||
}
|
}
|
||||||
|
|
||||||
lifetimeManager.AddEntry(newEntry);
|
Add(newEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeEntry(OsuHitObject hitObject)
|
private void removeEntry(OsuHitObject hitObject)
|
||||||
@ -118,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
entry.UnbindEvents();
|
entry.UnbindEvents();
|
||||||
|
|
||||||
lifetimeEntries.RemoveAt(index);
|
lifetimeEntries.RemoveAt(index);
|
||||||
lifetimeManager.RemoveEntry(entry);
|
Remove(entry);
|
||||||
|
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
{
|
{
|
||||||
@ -131,30 +120,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckChildrenLife()
|
protected override FollowPointConnection GetDrawable(FollowPointLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
bool anyAliveChanged = base.CheckChildrenLife();
|
var connection = connectionPool.Get();
|
||||||
anyAliveChanged |= lifetimeManager.Update(Time.Current);
|
connection.Pool = pointPool;
|
||||||
return anyAliveChanged;
|
connection.Apply(entry);
|
||||||
}
|
return connection;
|
||||||
|
|
||||||
private void onEntryBecameAlive(LifetimeEntry entry)
|
|
||||||
{
|
|
||||||
var connection = connectionPool.Get(c =>
|
|
||||||
{
|
|
||||||
c.Entry = (FollowPointLifetimeEntry)entry;
|
|
||||||
c.Pool = pointPool;
|
|
||||||
});
|
|
||||||
|
|
||||||
connectionsInUse[entry] = connection;
|
|
||||||
|
|
||||||
AddInternal(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onEntryBecameDead(LifetimeEntry entry)
|
|
||||||
{
|
|
||||||
RemoveInternal(connectionsInUse[entry]);
|
|
||||||
connectionsInUse.Remove(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStartTimeChanged(OsuHitObject hitObject)
|
private void onStartTimeChanged(OsuHitObject hitObject)
|
||||||
@ -171,16 +142,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
entry.UnbindEvents();
|
entry.UnbindEvents();
|
||||||
lifetimeEntries.Clear();
|
lifetimeEntries.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DrawablePoolNoLifetime<T> : DrawablePool<T>
|
|
||||||
where T : PoolableDrawable, new()
|
|
||||||
{
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
|
||||||
|
|
||||||
public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null)
|
|
||||||
: base(initialSize, maximumSize)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = textures.Get(@"Gameplay/osu/disc"),
|
Texture = textures.Get(@"Gameplay/osu/disc"),
|
||||||
},
|
},
|
||||||
|
new KiaiFlash
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
triangles = new TrianglesPiece
|
triangles = new TrianglesPiece
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
43
osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs
Normal file
43
osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.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.Audio.Track;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public class KiaiFlash : BeatSyncedContainer
|
||||||
|
{
|
||||||
|
private const double fade_length = 80;
|
||||||
|
|
||||||
|
private const float flash_opacity = 0.25f;
|
||||||
|
|
||||||
|
public KiaiFlash()
|
||||||
|
{
|
||||||
|
EarlyActivationMilliseconds = 80;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White,
|
||||||
|
Alpha = 0f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||||
|
{
|
||||||
|
if (!effectPoint.KiaiMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Child
|
||||||
|
.FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs
Normal file
61
osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.Audio.Track;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
|
{
|
||||||
|
internal class KiaiFlashingSprite : BeatSyncedContainer
|
||||||
|
{
|
||||||
|
private readonly Sprite mainSprite;
|
||||||
|
private readonly Sprite flashingSprite;
|
||||||
|
|
||||||
|
public Texture Texture
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mainSprite.Texture = value;
|
||||||
|
flashingSprite.Texture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float flash_opacity = 0.3f;
|
||||||
|
|
||||||
|
public KiaiFlashingSprite()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
mainSprite = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
flashingSprite = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||||
|
{
|
||||||
|
if (!effectPoint.KiaiMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
flashingSprite
|
||||||
|
.FadeTo(flash_opacity)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(timingPoint.BeatLength * 0.75f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -32,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Container<Sprite> circleSprites;
|
private Container circleSprites;
|
||||||
private Sprite hitCircleSprite;
|
private Drawable hitCircleSprite;
|
||||||
private Sprite hitCircleOverlay;
|
private Drawable hitCircleOverlay;
|
||||||
|
|
||||||
private SkinnableSpriteText hitCircleText;
|
private SkinnableSpriteText hitCircleText;
|
||||||
|
|
||||||
@ -72,20 +71,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
circleSprites = new Container<Sprite>
|
circleSprites = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
hitCircleSprite = new Sprite
|
hitCircleSprite = new KiaiFlashingSprite
|
||||||
{
|
{
|
||||||
Texture = baseTexture,
|
Texture = baseTexture,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
hitCircleOverlay = new Sprite
|
hitCircleOverlay = new KiaiFlashingSprite
|
||||||
{
|
{
|
||||||
Texture = overlayTexture,
|
Texture = overlayTexture,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Performance;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
@ -11,6 +8,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
internal class DrumRollHitContainer : ScrollingHitObjectContainer
|
internal class DrumRollHitContainer : ScrollingHitObjectContainer
|
||||||
{
|
{
|
||||||
|
// TODO: this usage is buggy.
|
||||||
|
// Because `LifetimeStart` is set based on scrolling, lifetime is not same as the time when the object is created.
|
||||||
|
// If the `Update` override is removed, it breaks in an obscure way.
|
||||||
|
protected override bool RemoveRewoundEntry => true;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -23,14 +25,5 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
Remove(flyingHit);
|
Remove(flyingHit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
|
||||||
{
|
|
||||||
base.OnChildLifetimeBoundaryCrossed(e);
|
|
||||||
|
|
||||||
// ensure all old hits are removed on becoming alive (may miss being in the AliveInternalChildren list above).
|
|
||||||
if (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward)
|
|
||||||
Remove((DrawableHitObject)e.Child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public override Container Overlays { get; }
|
public override Container Overlays { get; }
|
||||||
public override Container FrameStableComponents { get; }
|
public override Container FrameStableComponents { get; }
|
||||||
public override IFrameStableClock FrameStableClock { get; }
|
public override IFrameStableClock FrameStableClock { get; }
|
||||||
|
internal override bool FrameStablePlayback { get; set; }
|
||||||
public override IReadOnlyList<Mod> Mods { get; }
|
public override IReadOnlyList<Mod> Mods { get; }
|
||||||
|
|
||||||
public override double GameplayStartTime { get; }
|
public override double GameplayStartTime { get; }
|
||||||
|
@ -76,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator);
|
AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
sendFrames();
|
|
||||||
|
|
||||||
waitForPlayer();
|
waitForPlayer();
|
||||||
|
|
||||||
|
sendFrames();
|
||||||
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
||||||
|
|
||||||
AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame);
|
AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame);
|
||||||
@ -116,12 +116,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
start();
|
start();
|
||||||
|
|
||||||
loadSpectatingScreen();
|
loadSpectatingScreen();
|
||||||
|
waitForPlayer();
|
||||||
|
|
||||||
AddStep("advance frame count", () => nextFrame = 300);
|
AddStep("advance frame count", () => nextFrame = 300);
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
waitForPlayer();
|
|
||||||
|
|
||||||
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime > 30000);
|
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime > 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private double currentFrameStableTime
|
private double currentFrameStableTime
|
||||||
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
|
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
|
||||||
|
|
||||||
private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
||||||
|
|
||||||
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum BackgroundSource
|
public enum BackgroundSource
|
||||||
{
|
{
|
||||||
Skin,
|
Skin,
|
||||||
Beatmap
|
Beatmap,
|
||||||
|
|
||||||
|
[Description("Beatmap (with storyboard / video)")]
|
||||||
|
BeatmapWithStoryboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
|
{
|
||||||
|
public class BeatmapBackgroundWithStoryboard : BeatmapBackground
|
||||||
|
{
|
||||||
|
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
||||||
|
: base(beatmap, fallbackTextureName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
if (!Beatmap.Storyboard.HasDrawable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Beatmap.Storyboard.ReplacesBackground)
|
||||||
|
Sprite.Alpha = 0;
|
||||||
|
|
||||||
|
LoadComponentAsync(new AudioContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Volume = { Value = 0 },
|
||||||
|
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }
|
||||||
|
}, AddInternal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,9 +30,12 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ListItemBlock listItemBlock:
|
case ListItemBlock listItemBlock:
|
||||||
var isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered;
|
bool isOrdered = ((ListBlock)listItemBlock.Parent)?.IsOrdered == true;
|
||||||
var childContainer = CreateListItem(listItemBlock, level, isOrdered);
|
|
||||||
|
OsuMarkdownListItem childContainer = CreateListItem(listItemBlock, level, isOrdered);
|
||||||
|
|
||||||
container.Add(childContainer);
|
container.Add(childContainer);
|
||||||
|
|
||||||
foreach (var single in listItemBlock)
|
foreach (var single in listItemBlock)
|
||||||
base.AddMarkdownComponent(single, childContainer.Content, level);
|
base.AddMarkdownComponent(single, childContainer.Content, level);
|
||||||
break;
|
break;
|
||||||
|
@ -172,6 +172,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private class ScalingBackgroundScreen : BackgroundScreenDefault
|
private class ScalingBackgroundScreen : BackgroundScreenDefault
|
||||||
{
|
{
|
||||||
|
protected override bool AllowStoryboardBackground => false;
|
||||||
|
|
||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
{
|
{
|
||||||
this.FadeInFromZero(4000, Easing.OutQuint);
|
this.FadeInFromZero(4000, Easing.OutQuint);
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game
|
|||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
|
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.8;
|
||||||
|
|
||||||
public bool UseDevelopmentServer { get; }
|
public bool UseDevelopmentServer { get; }
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
|
|||||||
|
|
||||||
case ParagraphBlock paragraphBlock:
|
case ParagraphBlock paragraphBlock:
|
||||||
// Check if paragraph only contains an image
|
// Check if paragraph only contains an image
|
||||||
if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline)
|
if (paragraphBlock.Inline?.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline)
|
||||||
{
|
{
|
||||||
container.Add(new WikiMarkdownImageBlock(linkInline));
|
container.Add(new WikiMarkdownImageBlock(linkInline));
|
||||||
return;
|
return;
|
||||||
|
@ -0,0 +1,163 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Performance;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Objects.Pooling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container of <typeparamref name="TDrawable"/>s dynamically added/removed by model <typeparamref name="TEntry"/>s.
|
||||||
|
/// When an entry became alive, a drawable corresponding to the entry is obtained (potentially pooled), and added to this container.
|
||||||
|
/// The drawable is removed when the entry became dead.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntry">The type of entries managed by this container.</typeparam>
|
||||||
|
/// <typeparam name="TDrawable">The type of drawables corresponding to the entries.</typeparam>
|
||||||
|
public abstract class PooledDrawableWithLifetimeContainer<TEntry, TDrawable> : CompositeDrawable
|
||||||
|
where TEntry : LifetimeEntry
|
||||||
|
where TDrawable : Drawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All entries added to this container, including dead entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The enumeration order is undefined.
|
||||||
|
/// </remarks>
|
||||||
|
public IEnumerable<TEntry> Entries => allEntries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All alive entries and drawables corresponding to the entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The enumeration order is undefined.
|
||||||
|
/// </remarks>
|
||||||
|
public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to remove an entry when clock goes backward and crossed its <see cref="LifetimeEntry.LifetimeStart"/>.
|
||||||
|
/// Used when entries are dynamically added at its <see cref="LifetimeEntry.LifetimeStart"/> to prevent duplicated entries.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool RemoveRewoundEntry => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time prior to the current time within which entries should be considered alive.
|
||||||
|
/// </summary>
|
||||||
|
internal double PastLifetimeExtension { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time after the current time within which entries should be considered alive.
|
||||||
|
/// </summary>
|
||||||
|
internal double FutureLifetimeExtension { get; set; }
|
||||||
|
|
||||||
|
private readonly Dictionary<TEntry, TDrawable> aliveDrawableMap = new Dictionary<TEntry, TDrawable>();
|
||||||
|
private readonly HashSet<TEntry> allEntries = new HashSet<TEntry>();
|
||||||
|
|
||||||
|
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||||
|
|
||||||
|
protected PooledDrawableWithLifetimeContainer()
|
||||||
|
{
|
||||||
|
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||||
|
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||||
|
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a <typeparamref name="TEntry"/> to be managed by this container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The aliveness of the entry is not updated until <see cref="CheckChildrenLife"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void Add(TEntry entry)
|
||||||
|
{
|
||||||
|
allEntries.Add(entry);
|
||||||
|
lifetimeManager.AddEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a <typeparamref name="TEntry"/> from this container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the entry was alive, the corresponding drawable is removed.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>Whether the entry was in this container.</returns>
|
||||||
|
public virtual bool Remove(TEntry entry)
|
||||||
|
{
|
||||||
|
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||||
|
|
||||||
|
allEntries.Remove(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize new <typeparamref name="TDrawable"/> corresponding <paramref name="entry"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <typeparamref name="TDrawable"/> corresponding to the entry.</returns>
|
||||||
|
protected abstract TDrawable GetDrawable(TEntry entry);
|
||||||
|
|
||||||
|
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (TEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
TDrawable drawable = GetDrawable(entry);
|
||||||
|
aliveDrawableMap[entry] = drawable;
|
||||||
|
AddDrawable(entry, drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> to this container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Invoked when the entry became alive and a <typeparamref name="TDrawable"/> is obtained by <see cref="GetDrawable"/>.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void AddDrawable(TEntry entry, TDrawable drawable) => AddInternal(drawable);
|
||||||
|
|
||||||
|
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (TEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
TDrawable drawable = aliveDrawableMap[entry];
|
||||||
|
aliveDrawableMap.Remove(entry);
|
||||||
|
RemoveDrawable(entry, drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> from this container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Invoked when the entry became dead.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable);
|
||||||
|
|
||||||
|
private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||||
|
{
|
||||||
|
if (RemoveRewoundEntry && kind == LifetimeBoundaryKind.Start && direction == LifetimeBoundaryCrossingDirection.Backward)
|
||||||
|
Remove((TEntry)lifetimeEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove all <typeparamref name="TEntry"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var entry in Entries.ToArray())
|
||||||
|
Remove(entry);
|
||||||
|
|
||||||
|
Debug.Assert(aliveDrawableMap.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CheckChildrenLife()
|
||||||
|
{
|
||||||
|
bool aliveChanged = base.CheckChildrenLife();
|
||||||
|
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
||||||
|
return aliveChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,10 +68,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private bool frameStablePlayback = true;
|
private bool frameStablePlayback = true;
|
||||||
|
|
||||||
/// <summary>
|
internal override bool FrameStablePlayback
|
||||||
/// Whether to enable frame-stable playback.
|
|
||||||
/// </summary>
|
|
||||||
internal bool FrameStablePlayback
|
|
||||||
{
|
{
|
||||||
get => frameStablePlayback;
|
get => frameStablePlayback;
|
||||||
set
|
set
|
||||||
@ -431,6 +428,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IFrameStableClock FrameStableClock { get; }
|
public abstract IFrameStableClock FrameStableClock { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to enable frame-stable playback.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract bool FrameStablePlayback { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mods which are to be applied.
|
/// The mods which are to be applied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3,35 +3,23 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Performance;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public class HitObjectContainer : CompositeDrawable, IHitObjectContainer
|
public class HitObjectContainer : PooledDrawableWithLifetimeContainer<HitObjectLifetimeEntry, DrawableHitObject>, IHitObjectContainer
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All entries in this <see cref="HitObjectContainer"/> including dead entries.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<HitObjectLifetimeEntry> Entries => allEntries;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All alive entries and <see cref="DrawableHitObject"/>s used by the entries.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||||
@ -59,34 +47,16 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal event Action<HitObject> HitObjectUsageFinished;
|
internal event Action<HitObject> HitObjectUsageFinished;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of time prior to the current time within which <see cref="HitObject"/>s should be considered alive.
|
|
||||||
/// </summary>
|
|
||||||
internal double PastLifetimeExtension { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of time after the current time within which <see cref="HitObject"/>s should be considered alive.
|
|
||||||
/// </summary>
|
|
||||||
internal double FutureLifetimeExtension { get; set; }
|
|
||||||
|
|
||||||
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
||||||
|
|
||||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> aliveDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
|
||||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||||
|
|
||||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
|
||||||
private readonly HashSet<HitObjectLifetimeEntry> allEntries = new HashSet<HitObjectLifetimeEntry>();
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||||
|
|
||||||
public HitObjectContainer()
|
public HitObjectContainer()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
|
||||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
|
||||||
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
@ -99,63 +69,41 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Pooling support
|
#region Pooling support
|
||||||
|
|
||||||
public void Add(HitObjectLifetimeEntry entry)
|
public override bool Remove(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
allEntries.Add(entry);
|
if (!base.Remove(entry)) return false;
|
||||||
lifetimeManager.AddEntry(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(HitObjectLifetimeEntry entry)
|
|
||||||
{
|
|
||||||
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
|
||||||
|
|
||||||
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
|
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
|
||||||
if (nonPooledDrawableMap.Remove(entry, out var drawable))
|
if (nonPooledDrawableMap.Remove(entry, out var drawable))
|
||||||
removeDrawable(drawable);
|
removeDrawable(drawable);
|
||||||
|
|
||||||
allEntries.Remove(entry);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
if (nonPooledDrawableMap.TryGetValue(entry, out var drawable))
|
||||||
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
return drawable;
|
||||||
|
|
||||||
bool isPooled = !nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
return pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null) ??
|
||||||
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
if (drawable == null)
|
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
|
||||||
|
|
||||||
aliveDrawableMap[entry] = drawable;
|
|
||||||
|
|
||||||
if (isPooled)
|
|
||||||
{
|
|
||||||
addDrawable(drawable);
|
|
||||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnAdd(drawable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
if (nonPooledDrawableMap.ContainsKey(entry)) return;
|
||||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
|
||||||
|
|
||||||
var drawable = aliveDrawableMap[entry];
|
addDrawable(drawable);
|
||||||
bool isPooled = !nonPooledDrawableMap.ContainsKey(entry);
|
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
|
{
|
||||||
drawable.OnKilled();
|
drawable.OnKilled();
|
||||||
aliveDrawableMap.Remove(entry);
|
if (nonPooledDrawableMap.ContainsKey(entry)) return;
|
||||||
|
|
||||||
if (isPooled)
|
removeDrawable(drawable);
|
||||||
{
|
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||||
removeDrawable(drawable);
|
|
||||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnRemove(drawable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDrawable(DrawableHitObject drawable)
|
private void addDrawable(DrawableHitObject drawable)
|
||||||
@ -201,49 +149,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
||||||
|
|
||||||
private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
|
||||||
{
|
|
||||||
if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable))
|
|
||||||
OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked after a <see cref="DrawableHitObject"/> is added to this container.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnAdd(DrawableHitObject drawableHitObject)
|
|
||||||
{
|
|
||||||
Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked after a <see cref="DrawableHitObject"/> is removed from this container.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnRemove(DrawableHitObject drawableHitObject)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Clear()
|
|
||||||
{
|
|
||||||
lifetimeManager.ClearEntries();
|
|
||||||
foreach (var drawable in nonPooledDrawableMap.Values)
|
|
||||||
removeDrawable(drawable);
|
|
||||||
nonPooledDrawableMap.Clear();
|
|
||||||
Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CheckChildrenLife()
|
|
||||||
{
|
|
||||||
bool aliveChanged = base.CheckChildrenLife();
|
|
||||||
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
|
||||||
return aliveChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
|
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
|
||||||
private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r);
|
private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r);
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -45,13 +47,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
|
|
||||||
layoutComputed.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a position in screen space, return the time within this column.
|
/// Given a position in screen space, return the time within this column.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -147,17 +142,20 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAdd(DrawableHitObject drawableHitObject)
|
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
invalidateHitObject(drawableHitObject);
|
base.AddDrawable(entry, drawable);
|
||||||
drawableHitObject.DefaultsApplied += invalidateHitObject;
|
|
||||||
|
invalidateHitObject(drawable);
|
||||||
|
drawable.DefaultsApplied += invalidateHitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRemove(DrawableHitObject drawableHitObject)
|
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
layoutComputed.Remove(drawableHitObject);
|
base.RemoveDrawable(entry, drawable);
|
||||||
|
|
||||||
drawableHitObject.DefaultsApplied -= invalidateHitObject;
|
drawable.DefaultsApplied -= invalidateHitObject;
|
||||||
|
layoutComputed.Remove(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invalidateHitObject(DrawableHitObject hitObject)
|
private void invalidateHitObject(DrawableHitObject hitObject)
|
||||||
@ -206,6 +204,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
|
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
// Origin position may be relative to the parent size
|
||||||
|
Debug.Assert(hitObject.Parent != null);
|
||||||
|
|
||||||
float originAdjustment = 0.0f;
|
float originAdjustment = 0.0f;
|
||||||
|
|
||||||
// calculate the dimension of the part of the hitobject that should already be visible
|
// calculate the dimension of the part of the hitobject that should already be visible
|
||||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Screens.Backgrounds
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
|
protected virtual bool AllowStoryboardBackground => true;
|
||||||
|
|
||||||
public BackgroundScreenDefault(bool animateOnEnter = true)
|
public BackgroundScreenDefault(bool animateOnEnter = true)
|
||||||
: base(animateOnEnter)
|
: base(animateOnEnter)
|
||||||
{
|
{
|
||||||
@ -110,6 +112,12 @@ namespace osu.Game.Screens.Backgrounds
|
|||||||
newBackground = new BeatmapBackground(beatmap.Value, backgroundName);
|
newBackground = new BeatmapBackground(beatmap.Value, backgroundName);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BackgroundSource.BeatmapWithStoryboard:
|
||||||
|
newBackground = AllowStoryboardBackground
|
||||||
|
? new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName)
|
||||||
|
: new BeatmapBackground(beatmap.Value, backgroundName);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
newBackground = new SkinnedBackground(skin.Value, backgroundName);
|
newBackground = new SkinnedBackground(skin.Value, backgroundName);
|
||||||
break;
|
break;
|
||||||
|
@ -100,7 +100,13 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
// The source is stopped by a frequency fade first.
|
// The source is stopped by a frequency fade first.
|
||||||
if (isPaused.NewValue)
|
if (isPaused.NewValue)
|
||||||
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableSource.Stop());
|
{
|
||||||
|
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ =>
|
||||||
|
{
|
||||||
|
if (IsPaused.Value == isPaused.NewValue)
|
||||||
|
AdjustableSource.Stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
|
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
|
||||||
}
|
}
|
||||||
|
@ -81,10 +81,6 @@ namespace osu.Game.Screens.Play
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreManager scoreManager { get; set; }
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
private RulesetInfo rulesetInfo;
|
|
||||||
|
|
||||||
private Ruleset ruleset;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
@ -94,6 +90,10 @@ namespace osu.Game.Screens.Play
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
|
|
||||||
|
protected Ruleset GameplayRuleset { get; private set; }
|
||||||
|
|
||||||
|
protected GameplayBeatmap GameplayBeatmap { get; private set; }
|
||||||
|
|
||||||
private Sample sampleRestart;
|
private Sample sampleRestart;
|
||||||
|
|
||||||
public BreakOverlay BreakOverlay;
|
public BreakOverlay BreakOverlay;
|
||||||
@ -144,8 +144,6 @@ namespace osu.Game.Screens.Play
|
|||||||
Configuration = configuration ?? new PlayerConfiguration();
|
Configuration = configuration ?? new PlayerConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GameplayBeatmap GameplayBeatmap { get; private set; }
|
|
||||||
|
|
||||||
private ScreenSuspensionHandler screenSuspension;
|
private ScreenSuspensionHandler screenSuspension;
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
@ -164,7 +162,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// ensure the score is in a consistent state with the current player.
|
// ensure the score is in a consistent state with the current player.
|
||||||
Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo;
|
Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo;
|
||||||
Score.ScoreInfo.Ruleset = rulesetInfo;
|
Score.ScoreInfo.Ruleset = GameplayRuleset.RulesetInfo;
|
||||||
Score.ScoreInfo.Mods = Mods.Value.ToArray();
|
Score.ScoreInfo.Mods = Mods.Value.ToArray();
|
||||||
|
|
||||||
PrepareReplay();
|
PrepareReplay();
|
||||||
@ -211,16 +209,16 @@ namespace osu.Game.Screens.Play
|
|||||||
if (game is OsuGame osuGame)
|
if (game is OsuGame osuGame)
|
||||||
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
|
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
|
||||||
|
|
||||||
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
|
DrawableRuleset = GameplayRuleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
|
||||||
dependencies.CacheAs(DrawableRuleset);
|
dependencies.CacheAs(DrawableRuleset);
|
||||||
|
|
||||||
ScoreProcessor = ruleset.CreateScoreProcessor();
|
ScoreProcessor = GameplayRuleset.CreateScoreProcessor();
|
||||||
ScoreProcessor.ApplyBeatmap(playableBeatmap);
|
ScoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
ScoreProcessor.Mods.BindTo(Mods);
|
ScoreProcessor.Mods.BindTo(Mods);
|
||||||
|
|
||||||
dependencies.CacheAs(ScoreProcessor);
|
dependencies.CacheAs(ScoreProcessor);
|
||||||
|
|
||||||
HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
|
HealthProcessor = GameplayRuleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
|
||||||
HealthProcessor.ApplyBeatmap(playableBeatmap);
|
HealthProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
dependencies.CacheAs(HealthProcessor);
|
dependencies.CacheAs(HealthProcessor);
|
||||||
@ -239,7 +237,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||||
// full access to all skin sources.
|
// full access to all skin sources.
|
||||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
var rulesetSkinProvider = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
||||||
|
|
||||||
// load the skinning hierarchy first.
|
// load the skinning hierarchy first.
|
||||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||||
@ -254,7 +252,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
|
// also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
|
||||||
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
|
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
|
||||||
var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
var hudRulesetContainer = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
||||||
|
|
||||||
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
||||||
GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value)));
|
GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value)));
|
||||||
@ -480,18 +478,18 @@ namespace osu.Game.Screens.Play
|
|||||||
if (Beatmap.Value.Beatmap == null)
|
if (Beatmap.Value.Beatmap == null)
|
||||||
throw new InvalidOperationException("Beatmap was not loaded");
|
throw new InvalidOperationException("Beatmap was not loaded");
|
||||||
|
|
||||||
rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
|
var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
|
||||||
ruleset = rulesetInfo.CreateInstance();
|
GameplayRuleset = rulesetInfo.CreateInstance();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value);
|
playable = Beatmap.Value.GetPlayableBeatmap(GameplayRuleset.RulesetInfo, Mods.Value);
|
||||||
}
|
}
|
||||||
catch (BeatmapInvalidForRulesetException)
|
catch (BeatmapInvalidForRulesetException)
|
||||||
{
|
{
|
||||||
// A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset
|
// A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset
|
||||||
rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset;
|
rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset;
|
||||||
ruleset = rulesetInfo.CreateInstance();
|
GameplayRuleset = rulesetInfo.CreateInstance();
|
||||||
|
|
||||||
playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value);
|
playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value);
|
||||||
}
|
}
|
||||||
@ -585,6 +583,29 @@ namespace osu.Game.Screens.Play
|
|||||||
/// <param name="time">The destination time to seek to.</param>
|
/// <param name="time">The destination time to seek to.</param>
|
||||||
public void Seek(double time) => GameplayClockContainer.Seek(time);
|
public void Seek(double time) => GameplayClockContainer.Seek(time);
|
||||||
|
|
||||||
|
private ScheduledDelegate frameStablePlaybackResetDelegate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seeks to a specific time in gameplay, bypassing frame stability.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Intermediate hitobject judgements may not be applied or reverted correctly during this seek.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="time">The destination time to seek to.</param>
|
||||||
|
internal void NonFrameStableSeek(double time)
|
||||||
|
{
|
||||||
|
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
|
||||||
|
frameStablePlaybackResetDelegate.RunTask();
|
||||||
|
|
||||||
|
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
|
||||||
|
DrawableRuleset.FrameStablePlayback = false;
|
||||||
|
|
||||||
|
Seek(time);
|
||||||
|
|
||||||
|
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
|
||||||
|
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
|
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
|
||||||
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
|
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
|
||||||
@ -918,11 +939,10 @@ namespace osu.Game.Screens.Play
|
|||||||
/// Creates the player's <see cref="Scoring.Score"/>.
|
/// Creates the player's <see cref="Scoring.Score"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The <see cref="Scoring.Score"/>.</returns>
|
/// <returns>The <see cref="Scoring.Score"/>.</returns>
|
||||||
protected virtual Score CreateScore() =>
|
protected virtual Score CreateScore() => new Score
|
||||||
new Score
|
{
|
||||||
{
|
ScoreInfo = new ScoreInfo { User = api.LocalUser.Value },
|
||||||
ScoreInfo = new ScoreInfo { User = api.LocalUser.Value },
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Imports the player's <see cref="Scoring.Score"/> to the local database.
|
/// Imports the player's <see cref="Scoring.Score"/> to the local database.
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
|
|
||||||
@ -16,11 +16,11 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
public class SpectatorPlayer : Player
|
public class SpectatorPlayer : Player
|
||||||
{
|
{
|
||||||
private readonly Score score;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
|
|
||||||
|
private readonly Score score;
|
||||||
|
|
||||||
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
|
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
|
||||||
|
|
||||||
public SpectatorPlayer(Score score)
|
public SpectatorPlayer(Score score)
|
||||||
@ -28,11 +28,6 @@ namespace osu.Game.Screens.Play
|
|||||||
this.score = score;
|
this.score = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Score CreateScore() => score;
|
|
||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
|
||||||
=> new SpectatorResultsScreen(score);
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -48,25 +43,66 @@ namespace osu.Game.Screens.Play
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void StartGameplay()
|
||||||
|
{
|
||||||
|
base.StartGameplay();
|
||||||
|
|
||||||
|
spectatorClient.OnNewFrames += userSentFrames;
|
||||||
|
seekToGameplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void userSentFrames(int userId, FrameDataBundle bundle)
|
||||||
|
{
|
||||||
|
if (userId != score.ScoreInfo.User.Id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!this.IsCurrentScreen())
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var frame in bundle.Frames)
|
||||||
|
{
|
||||||
|
IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame();
|
||||||
|
convertibleFrame.FromLegacy(frame, GameplayBeatmap.PlayableBeatmap);
|
||||||
|
|
||||||
|
var convertedFrame = (ReplayFrame)convertibleFrame;
|
||||||
|
convertedFrame.Time = frame.Time;
|
||||||
|
|
||||||
|
score.Replay.Frames.Add(convertedFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
seekToGameplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool seekedToGameplay;
|
||||||
|
|
||||||
|
private void seekToGameplay()
|
||||||
|
{
|
||||||
|
if (seekedToGameplay || score.Replay.Frames.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NonFrameStableSeek(score.Replay.Frames[0].Time);
|
||||||
|
|
||||||
|
seekedToGameplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Score CreateScore() => score;
|
||||||
|
|
||||||
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
|
=> new SpectatorResultsScreen(score);
|
||||||
|
|
||||||
protected override void PrepareReplay()
|
protected override void PrepareReplay()
|
||||||
{
|
{
|
||||||
DrawableRuleset?.SetReplayScore(score);
|
DrawableRuleset?.SetReplayScore(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
|
|
||||||
{
|
|
||||||
// if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap.
|
|
||||||
double? firstFrameTime = score.Replay.Frames.FirstOrDefault()?.Time;
|
|
||||||
|
|
||||||
if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000)
|
|
||||||
return base.CreateGameplayClockContainer(beatmap, gameplayStart);
|
|
||||||
|
|
||||||
return new MasterGameplayClockContainer(beatmap, firstFrameTime.Value, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
||||||
|
spectatorClient.OnNewFrames -= userSentFrames;
|
||||||
|
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +121,10 @@ namespace osu.Game.Screens.Play
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (spectatorClient != null)
|
if (spectatorClient != null)
|
||||||
|
{
|
||||||
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
||||||
|
spectatorClient.OnNewFrames -= userSentFrames;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Replays;
|
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -71,8 +69,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
|
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
|
||||||
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
|
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
|
||||||
|
|
||||||
spectatorClient.OnNewFrames += userSentFrames;
|
|
||||||
|
|
||||||
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
||||||
managerUpdated.BindValueChanged(beatmapUpdated);
|
managerUpdated.BindValueChanged(beatmapUpdated);
|
||||||
|
|
||||||
@ -197,29 +193,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
Schedule(() => StartGameplay(userId, gameplayState));
|
Schedule(() => StartGameplay(userId, gameplayState));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void userSentFrames(int userId, FrameDataBundle bundle)
|
|
||||||
{
|
|
||||||
if (!userMap.ContainsKey(userId))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!gameplayStates.TryGetValue(userId, out var gameplayState))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The ruleset instance should be guaranteed to be in sync with the score via ScoreLock.
|
|
||||||
Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset));
|
|
||||||
|
|
||||||
foreach (var frame in bundle.Frames)
|
|
||||||
{
|
|
||||||
IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame();
|
|
||||||
convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap);
|
|
||||||
|
|
||||||
var convertedFrame = (ReplayFrame)convertibleFrame;
|
|
||||||
convertedFrame.Time = frame.Time;
|
|
||||||
|
|
||||||
gameplayState.Score.Replay.Frames.Add(convertedFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a spectated user's state has changed.
|
/// Invoked when a spectated user's state has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -260,8 +233,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
|
|
||||||
if (spectatorClient != null)
|
if (spectatorClient != null)
|
||||||
{
|
{
|
||||||
spectatorClient.OnNewFrames -= userSentFrames;
|
|
||||||
|
|
||||||
foreach (var (userId, _) in userMap)
|
foreach (var (userId, _) in userMap)
|
||||||
spectatorClient.StopWatchingUser(userId);
|
spectatorClient.StopWatchingUser(userId);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
dummyAPI = new DummyAPIAccess();
|
dummyAPI = new DummyAPIAccess();
|
||||||
Dependencies.CacheAs<IAPIProvider>(dummyAPI);
|
Dependencies.CacheAs<IAPIProvider>(dummyAPI);
|
||||||
Add(dummyAPI);
|
base.Content.Add(dummyAPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Dependencies;
|
return Dependencies;
|
||||||
|
@ -75,5 +75,10 @@ namespace osu.Game.Utils
|
|||||||
/// </param>
|
/// </param>
|
||||||
public static float NextSingle(int seed, int series = 0) =>
|
public static float NextSingle(int seed, int series = 0) =>
|
||||||
(float)(NextULong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision
|
(float)(NextULong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute a random floating point value between <paramref name="min"/> and <paramref name="max"/> from given seed and series number.
|
||||||
|
/// </summary>
|
||||||
|
public static float NextSingle(float min, float max, int seed, int series = 0) => min + NextSingle(seed, series) * (max - min);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.601.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.607.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.4.0" />
|
<PackageReference Include="Sentry" Version="3.4.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.601.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.607.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.601.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.607.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user