mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 02:22:56 +08:00
Merge remote-tracking branch 'upstream/master' into combo-colors
This commit is contained in:
commit
00b68b050c
@ -191,4 +191,7 @@ dotnet_diagnostic.IDE0052.severity = silent
|
||||
#Rules for disposable
|
||||
dotnet_diagnostic.IDE0067.severity = none
|
||||
dotnet_diagnostic.IDE0068.severity = none
|
||||
dotnet_diagnostic.IDE0069.severity = none
|
||||
dotnet_diagnostic.IDE0069.severity = none
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
@ -16,7 +16,7 @@
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.0" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
@ -9,7 +9,9 @@
|
||||
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
|
||||
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
||||
|
||||
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||
|
||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
||||
|
||||
## Status
|
||||
|
||||
|
@ -113,7 +113,7 @@ platform :ios do
|
||||
|
||||
souyuz(
|
||||
platform: "ios",
|
||||
plist_path: "../osu.iOS/Info.plist"
|
||||
plist_path: "osu.iOS/Info.plist"
|
||||
)
|
||||
end
|
||||
|
||||
@ -127,7 +127,7 @@ platform :ios do
|
||||
end
|
||||
|
||||
lane :update_version do |options|
|
||||
options[:plist_path] = '../osu.iOS/Info.plist'
|
||||
options[:plist_path] = 'osu.iOS/Info.plist'
|
||||
app_version(options)
|
||||
end
|
||||
|
||||
|
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.812.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.814.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.819.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>click the circles. to the beat.</Description>
|
||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||
<AssemblyName>osu!</AssemblyName>
|
||||
<Title>osu!lazer</Title>
|
||||
<Product>osu!lazer</Product>
|
||||
|
@ -9,8 +9,7 @@
|
||||
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
||||
<iconUrl>https://puu.sh/tYyXZ/9a01a5d1b0.ico</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>click the circles. to the beat.</description>
|
||||
<summary>click the circles.</summary>
|
||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||
<releaseNotes>testing</releaseNotes>
|
||||
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
||||
<language>en-AU</language>
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
|
||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
84
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
Normal file
84
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public class TestSceneCatchModRelax : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestModRelax() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModRelax(),
|
||||
Autoplay = false,
|
||||
PassCondition = passCondition,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 0
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 250
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 500
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 750,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private bool passCondition()
|
||||
{
|
||||
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||
|
||||
switch (Player.ScoreProcessor.Combo.Value)
|
||||
{
|
||||
case 0:
|
||||
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
|
||||
break;
|
||||
}
|
||||
|
||||
return Player.ScoreProcessor.Combo.Value >= 6;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
@ -38,7 +40,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
new Vector2(width, 0)
|
||||
}),
|
||||
StartTime = i * 2000,
|
||||
NewCombo = i % 8 == 0
|
||||
NewCombo = i % 8 == 0,
|
||||
Samples = new List<HitSampleInfo>(new[]
|
||||
{
|
||||
new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -20,19 +20,19 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
|
||||
|
||||
AddStep("show droplet", () => SetContents(createDrawableDroplet));
|
||||
|
||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
|
||||
|
||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||
}
|
||||
|
||||
private Drawable createDrawableTinyDroplet()
|
||||
{
|
||||
var droplet = new TinyDroplet
|
||||
var droplet = new TestCatchTinyDroplet
|
||||
{
|
||||
StartTime = Clock.CurrentTime,
|
||||
Scale = 1.5f,
|
||||
};
|
||||
|
||||
@ -47,12 +47,12 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
};
|
||||
}
|
||||
|
||||
private Drawable createDrawableDroplet()
|
||||
private Drawable createDrawableDroplet(bool hyperdash = false)
|
||||
{
|
||||
var droplet = new Droplet
|
||||
var droplet = new TestCatchDroplet
|
||||
{
|
||||
StartTime = Clock.CurrentTime,
|
||||
Scale = 1.5f,
|
||||
HyperDashTarget = hyperdash ? new Banana() : null
|
||||
};
|
||||
|
||||
return new DrawableDroplet(droplet)
|
||||
@ -95,5 +95,21 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
||||
}
|
||||
|
||||
public class TestCatchDroplet : Droplet
|
||||
{
|
||||
public TestCatchDroplet()
|
||||
{
|
||||
StartTime = 1000000000000;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestCatchTinyDroplet : TinyDroplet
|
||||
{
|
||||
public TestCatchTinyDroplet()
|
||||
{
|
||||
StartTime = 1000000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -18,23 +19,43 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
private int hyperDashCount;
|
||||
private bool inHyperDash;
|
||||
|
||||
[Test]
|
||||
public void TestHyperDash()
|
||||
{
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
|
||||
|
||||
AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
AddStep("reset count", () =>
|
||||
{
|
||||
AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
|
||||
AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing);
|
||||
inHyperDash = false;
|
||||
hyperDashCount = 0;
|
||||
|
||||
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
|
||||
Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
|
||||
{
|
||||
var catcher = Player.ChildrenOfType<CatcherArea>().FirstOrDefault()?.MovableCatcher;
|
||||
|
||||
if (catcher == null)
|
||||
return;
|
||||
|
||||
if (catcher.HyperDashing != inHyperDash)
|
||||
{
|
||||
inHyperDash = catcher.HyperDashing;
|
||||
if (catcher.HyperDashing)
|
||||
hyperDashCount++;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
int count = i + 1;
|
||||
AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
|
||||
}
|
||||
}
|
||||
|
||||
private Catcher getCatcher() => Player.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
@ -46,6 +67,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
}
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
// Should produce a hyper-dash (edge case test)
|
||||
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
|
||||
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
|
||||
@ -63,6 +86,20 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
createObjects(() => new Fruit { X = right_x });
|
||||
createObjects(() => new TestJuiceStream(left_x), 1);
|
||||
|
||||
beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint
|
||||
{
|
||||
BeatLength = 50
|
||||
});
|
||||
|
||||
createObjects(() => new TestJuiceStream(left_x)
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero),
|
||||
new PathControlPoint(new Vector2(512, 0))
|
||||
})
|
||||
}, 1);
|
||||
|
||||
return beatmap;
|
||||
|
||||
void createObjects(Func<CatchHitObject> createObject, int count = 3)
|
||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
if (amount > 0)
|
||||
{
|
||||
// Clamp to the right bound
|
||||
if (position + amount < 1)
|
||||
if (position + amount < CatchPlayfield.WIDTH)
|
||||
position += amount;
|
||||
}
|
||||
else
|
||||
@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
|
||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||
|
||||
// Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
|
||||
// This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
|
||||
// For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size.
|
||||
halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE;
|
||||
|
||||
int lastDirection = 0;
|
||||
double lastExcess = halfCatcherWidth;
|
||||
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
|
||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this object can be placed on the catcher's plate.
|
||||
/// </summary>
|
||||
public virtual bool CanBePlated => false;
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// </summary>
|
||||
@ -100,6 +105,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject
|
||||
{
|
||||
public override bool CanBePlated => true;
|
||||
}
|
||||
|
||||
public enum FruitVisualRepresentation
|
||||
{
|
||||
Pear,
|
||||
|
@ -15,14 +15,12 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class PalpableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
||||
where TObject : CatchHitObject
|
||||
public abstract class PalpableDrawableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
||||
where TObject : PalpableCatchHitObject
|
||||
{
|
||||
public override bool CanBePlated => true;
|
||||
|
||||
protected Container ScaleContainer { get; private set; }
|
||||
|
||||
protected PalpableCatchHitObject(TObject hitObject)
|
||||
protected PalpableDrawableCatchHitObject(TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
@ -65,9 +63,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||
{
|
||||
public virtual bool CanBePlated => false;
|
||||
|
||||
public virtual bool StaysOnPlate => CanBePlated;
|
||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
||||
|
||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||
|
||||
|
@ -4,12 +4,11 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableDroplet : PalpableCatchHitObject<Droplet>
|
||||
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
||||
{
|
||||
public override bool StaysOnPlate => false;
|
||||
|
||||
@ -21,11 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp
|
||||
{
|
||||
Size = Size / 4,
|
||||
AccentColour = { BindTarget = AccentColour }
|
||||
});
|
||||
ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece());
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableFruit : PalpableCatchHitObject<Fruit>
|
||||
public class DrawableFruit : PalpableDrawableCatchHitObject<Fruit>
|
||||
{
|
||||
public DrawableFruit(Fruit h)
|
||||
: base(h)
|
||||
|
69
osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
Normal file
69
osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DropletPiece : CompositeDrawable
|
||||
{
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
InternalChild = new Pulp
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||
};
|
||||
|
||||
if (hitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(2f),
|
||||
Depth = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 6,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -21,11 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
public const float RADIUS_ADJUST = 1.1f;
|
||||
|
||||
private Circle border;
|
||||
|
||||
private CatchHitObject hitObject;
|
||||
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
public FruitPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -37,8 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = Size.X / 2,
|
||||
Radius = DrawWidth / 2,
|
||||
Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f)
|
||||
};
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class Droplet : CatchHitObject
|
||||
public class Droplet : PalpableCatchHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new CatchDropletJudgement();
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class Fruit : CatchHitObject
|
||||
public class Fruit : PalpableCatchHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new CatchJudgement();
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 3368,
|
||||
"Objects": [{
|
||||
"StartTime": 3368,
|
||||
"Position": 374
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 3501,
|
||||
"Objects": [{
|
||||
"StartTime": 3501,
|
||||
"Position": 446
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4
|
||||
OverallDifficulty:9.6
|
||||
ApproachRate:9.6
|
||||
SliderMultiplier:1.9
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
2169,266.666666666667,4,2,1,70,1,0
|
||||
|
||||
[HitObjects]
|
||||
374,60,3368,1,0,0:0:0:0:
|
||||
410,146,3501,1,2,0:1:0:0:
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
@ -61,7 +62,12 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
switch (lookup)
|
||||
{
|
||||
case CatchSkinColour colour:
|
||||
return Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||
var result = (Bindable<Color4>)Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
|
||||
return (IBindable<TValue>)result;
|
||||
}
|
||||
|
||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
||||
|
@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
colouredSprite = new Sprite
|
||||
{
|
||||
Texture = skin.GetTexture(lookupName),
|
||||
Colour = drawableObject.AccentColour.Value,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true);
|
||||
accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,15 +10,21 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||
{
|
||||
private const float playfield_size_adjust = 0.8f;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
public CatchPlayfieldAdjustmentContainer()
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
// because we are using centre anchor/origin, we will need to limit visibility in the future
|
||||
// to ensure tall windows do not get a readability advantage.
|
||||
// it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
|
||||
// which are compatible with TopCentre alignment.
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
|
||||
Size = new Vector2(playfield_size_adjust);
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
@ -27,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
FillAspectRatio = 4f / 3,
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, }
|
||||
};
|
||||
}
|
||||
|
||||
@ -40,8 +46,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// in stable, fruit fall vertically from -100 to 340.
|
||||
// to emulate this, we want to make our playfield 440 gameplay pixels high.
|
||||
// we then offset it -100 vertically in the position set below.
|
||||
const float stable_v_offset_ratio = 440 / 384f;
|
||||
|
||||
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
|
||||
Size = Vector2.Divide(Vector2.One, Scale);
|
||||
Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
|
||||
Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <summary>
|
||||
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
||||
/// </summary>
|
||||
private const float allowed_catch_range = 0.8f;
|
||||
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// The drawable catcher for <see cref="CurrentState"/>.
|
||||
@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
/// <param name="scale">The scale of the catcher.</param>
|
||||
internal static float CalculateCatchWidth(Vector2 scale)
|
||||
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
|
||||
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||
@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
public bool AttemptCatch(CatchHitObject fruit)
|
||||
{
|
||||
if (!fruit.CanBePlated)
|
||||
return false;
|
||||
|
||||
var halfCatchWidth = catchWidth * 0.5f;
|
||||
|
||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||
@ -226,9 +229,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||
|
||||
// only update hyperdash state if we are catching a fruit.
|
||||
// exceptions are Droplets and JuiceStreams.
|
||||
if (!(fruit is Fruit)) return validCatch;
|
||||
// only update hyperdash state if we are not catching a tiny droplet.
|
||||
if (fruit is TinyDroplet) return validCatch;
|
||||
|
||||
if (validCatch && fruit.HyperDash)
|
||||
{
|
||||
@ -283,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private void runHyperDashStateTransition(bool hyperDashing)
|
||||
{
|
||||
trails.HyperDashTrailsColour = hyperDashColour;
|
||||
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
||||
updateTrailVisibility();
|
||||
|
||||
if (hyperDashing)
|
||||
@ -401,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
||||
hyperDashColour;
|
||||
|
||||
trails.HyperDashTrailsColour = hyperDashColour;
|
||||
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
||||
|
||||
runHyperDashStateTransition(HyperDashing);
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
||||
}
|
||||
|
||||
if (result.IsHit && fruit.CanBePlated)
|
||||
if (result.IsHit && fruit.HitObject.CanBePlated)
|
||||
{
|
||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
||||
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
||||
|
||||
private Color4 hyperDashTrailsColour;
|
||||
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
public Color4 HyperDashTrailsColour
|
||||
{
|
||||
@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
return;
|
||||
|
||||
hyperDashTrailsColour = value;
|
||||
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
hyperDashTrails.Colour = hyperDashTrailsColour;
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 endGlowSpritesColour;
|
||||
private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
public Color4 EndGlowSpritesColour
|
||||
{
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
return;
|
||||
|
||||
endGlowSpritesColour = value;
|
||||
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
endGlowSprites.Colour = endGlowSpritesColour;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
// 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.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public class TestSceneManiaModInvert : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestInversion() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModInvert(),
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
||||
});
|
||||
}
|
||||
}
|
@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[Cached]
|
||||
private readonly Column column;
|
||||
|
||||
public ColumnTestContainer(int column, ManiaAction action)
|
||||
public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
|
||||
{
|
||||
this.column = new Column(column)
|
||||
InternalChildren = new[]
|
||||
{
|
||||
Action = { Value = action },
|
||||
AccentColour = Color4.Orange,
|
||||
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
|
||||
};
|
||||
|
||||
InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
this.column = new Column(column)
|
||||
{
|
||||
Action = { Value = action },
|
||||
AccentColour = Color4.Orange,
|
||||
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
|
||||
Alpha = showColumn ? 1 : 0
|
||||
},
|
||||
content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
this.column.TopLevelContainer.CreateProxy()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ColumnTestContainer(0, ManiaAction.Key1)
|
||||
new ColumnTestContainer(0, ManiaAction.Key1, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
}));
|
||||
})
|
||||
},
|
||||
new ColumnTestContainer(1, ManiaAction.Key2)
|
||||
new ColumnTestContainer(1, ManiaAction.Key2, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
|
||||
_ => new DefaultStageBackground())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
|
||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -21,14 +21,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
/// </summary>
|
||||
/// <param name="column">The 0-based column index.</param>
|
||||
/// <returns>Whether the column is a special column.</returns>
|
||||
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
|
||||
public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
|
||||
|
||||
/// <summary>
|
||||
/// Get the type of column given a column index.
|
||||
/// </summary>
|
||||
/// <param name="column">The 0-based column index.</param>
|
||||
/// <returns>The type of the column.</returns>
|
||||
public ColumnType GetTypeOfColumn(int column)
|
||||
public readonly ColumnType GetTypeOfColumn(int column)
|
||||
{
|
||||
if (IsSpecialColumn(column))
|
||||
return ColumnType.Special;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
|
||||
@ -15,7 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
|
||||
AccentColour.Value = colours.Yellow;
|
||||
|
||||
Background.Alpha = 0.5f;
|
||||
Foreground.Alpha = 0;
|
||||
}
|
||||
|
||||
protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
||||
{
|
||||
public class HoldNoteTickJudgement : ManiaJudgement
|
||||
{
|
||||
protected override int NumericResultFor(HitResult result) => 20;
|
||||
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0;
|
||||
|
||||
protected override double HealthIncreaseFor(HitResult result)
|
||||
{
|
||||
|
@ -220,6 +220,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModDualStages(),
|
||||
new ManiaModMirror(),
|
||||
new ManiaModDifficultyAdjust(),
|
||||
new ManiaModInvert(),
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
@ -325,6 +326,16 @@ namespace osu.Game.Rulesets.Mania
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(score.HitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -14,15 +15,23 @@ namespace osu.Game.Rulesets.Mania
|
||||
/// </summary>
|
||||
public readonly int? TargetColumn;
|
||||
|
||||
/// <summary>
|
||||
/// The intended <see cref="StageDefinition"/> for this component.
|
||||
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
||||
/// </summary>
|
||||
public readonly StageDefinition? StageDefinition;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="component">The component.</param>
|
||||
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null)
|
||||
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
|
||||
: base(component)
|
||||
{
|
||||
TargetColumn = targetColumn;
|
||||
StageDefinition = stageDefinition;
|
||||
}
|
||||
|
||||
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
|
||||
|
76
osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
Normal file
76
osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion
|
||||
{
|
||||
public override string Name => "Invert";
|
||||
|
||||
public override string Acronym => "IN";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override string Description => "Hold the keys. To the beat.";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
var newObjects = new List<ManiaHitObject>();
|
||||
|
||||
foreach (var column in maniaBeatmap.HitObjects.GroupBy(h => h.Column))
|
||||
{
|
||||
var newColumnObjects = new List<ManiaHitObject>();
|
||||
|
||||
var locations = column.OfType<Note>().Select(n => (startTime: n.StartTime, samples: n.Samples))
|
||||
.Concat(column.OfType<HoldNote>().SelectMany(h => new[]
|
||||
{
|
||||
(startTime: h.StartTime, samples: h.GetNodeSamples(0)),
|
||||
(startTime: h.EndTime, samples: h.GetNodeSamples(1))
|
||||
}))
|
||||
.OrderBy(h => h.startTime).ToList();
|
||||
|
||||
for (int i = 0; i < locations.Count - 1; i++)
|
||||
{
|
||||
// Full duration of the hold note.
|
||||
double duration = locations[i + 1].startTime - locations[i].startTime;
|
||||
|
||||
// Beat length at the end of the hold note.
|
||||
double beatLength = beatmap.ControlPointInfo.TimingPointAt(locations[i + 1].startTime).BeatLength;
|
||||
|
||||
// Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes.
|
||||
duration = Math.Max(duration / 2, duration - beatLength / 4);
|
||||
|
||||
newColumnObjects.Add(new HoldNote
|
||||
{
|
||||
Column = column.Key,
|
||||
StartTime = locations[i].startTime,
|
||||
Duration = duration,
|
||||
NodeSamples = new List<IList<HitSampleInfo>> { locations[i].samples, Array.Empty<HitSampleInfo>() }
|
||||
});
|
||||
}
|
||||
|
||||
newObjects.AddRange(newColumnObjects);
|
||||
}
|
||||
|
||||
maniaBeatmap.HitObjects = newObjects.OrderBy(h => h.StartTime).ToList();
|
||||
|
||||
// No breaks
|
||||
maniaBeatmap.Breaks.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
@ -32,7 +33,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
||||
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
||||
|
||||
private readonly Drawable bodyPiece;
|
||||
/// <summary>
|
||||
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
||||
/// </summary>
|
||||
private readonly Container sizingContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
||||
/// </summary>
|
||||
private readonly Container maskingContainer;
|
||||
|
||||
private readonly SkinnableDrawable bodyPiece;
|
||||
|
||||
/// <summary>
|
||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||
@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
public bool HasBroken { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the hold note has been released potentially without having caused a break.
|
||||
/// </summary>
|
||||
private double? releaseTime;
|
||||
|
||||
public DrawableHoldNote(HoldNote hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
AddRangeInternal(new[]
|
||||
Container maskedContents;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
sizingContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
maskingContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = maskedContents = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
}
|
||||
},
|
||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
},
|
||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X
|
||||
},
|
||||
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
|
||||
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
||||
});
|
||||
|
||||
maskedContents.AddRange(new[]
|
||||
{
|
||||
bodyPiece.CreateProxy(),
|
||||
tickContainer.CreateProxy(),
|
||||
tailContainer.CreateProxy(),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
base.OnDirectionChanged(e);
|
||||
|
||||
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
if (e.NewValue == ScrollingDirection.Up)
|
||||
{
|
||||
bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft;
|
||||
sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft;
|
||||
sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PlaySamples()
|
||||
@ -135,13 +185,48 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// Samples are played by the head/tail notes.
|
||||
}
|
||||
|
||||
public override void OnKilled()
|
||||
{
|
||||
base.OnKilled();
|
||||
(bodyPiece.Drawable as IHoldNoteBody)?.Recycle();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Make the body piece not lie under the head note
|
||||
if (Time.Current < releaseTime)
|
||||
releaseTime = null;
|
||||
|
||||
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
|
||||
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
|
||||
sizingContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0,
|
||||
Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0,
|
||||
};
|
||||
|
||||
// Pad the masking container to the starting position of the body piece (half-way under the head).
|
||||
// This is required to make the body start getting masked immediately as soon as the note is held.
|
||||
maskingContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0,
|
||||
Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0,
|
||||
};
|
||||
|
||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||
|
||||
// As the note is being held, adjust the size of the sizing container. This has two effects:
|
||||
// 1. The contained masking container will mask the body and ticks.
|
||||
// 2. The head note will move along with the new "head position" in the container.
|
||||
if (Head.IsHit && releaseTime == null)
|
||||
{
|
||||
// How far past the hit target this hold note is. Always a positive value.
|
||||
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
|
||||
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
@ -153,7 +238,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (Tail.AllJudged)
|
||||
{
|
||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
||||
endHold();
|
||||
}
|
||||
|
||||
if (Tail.Result.Type == HitResult.Miss)
|
||||
HasBroken = true;
|
||||
@ -206,6 +294,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// If the key has been released too early, the user should not receive full score for the release
|
||||
if (!Tail.IsHit)
|
||||
HasBroken = true;
|
||||
|
||||
releaseTime = Time.Current;
|
||||
}
|
||||
|
||||
private void endHold()
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
/// <summary>
|
||||
@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
public void UpdateResult() => base.UpdateResult(true);
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
// This hitobject should never expire, so this is just a safe maximum.
|
||||
LifetimeEnd = LifetimeStart + 30000;
|
||||
}
|
||||
|
||||
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
|
||||
|
||||
public override void OnReleased(ManiaAction action)
|
||||
|
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
break;
|
||||
|
||||
case ArmedState.Hit:
|
||||
this.FadeOut(150, Easing.OutQuint);
|
||||
this.FadeOut();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -19,24 +19,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
/// <summary>
|
||||
/// Represents length-wise portion of a hold note.
|
||||
/// </summary>
|
||||
public class DefaultBodyPiece : CompositeDrawable
|
||||
public class DefaultBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||
{
|
||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||
|
||||
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
|
||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||
|
||||
protected Drawable Background { get; private set; }
|
||||
protected BufferedContainer Foreground { get; private set; }
|
||||
|
||||
private BufferedContainer subtractionContainer;
|
||||
private Container subtractionLayer;
|
||||
private Container foregroundContainer;
|
||||
|
||||
public DefaultBodyPiece()
|
||||
{
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
AddLayout(subtractionCache);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
@ -45,7 +38,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
InternalChildren = new[]
|
||||
{
|
||||
Background = new Box { RelativeSizeAxes = Axes.Both },
|
||||
Foreground = new BufferedContainer
|
||||
foregroundContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
if (drawableObject != null)
|
||||
{
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
AccentColour.BindTo(drawableObject.AccentColour);
|
||||
IsHitting.BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||
|
||||
Recycle();
|
||||
}
|
||||
|
||||
public void Recycle() => foregroundContainer.Child = CreateForeground();
|
||||
|
||||
protected virtual Drawable CreateForeground() => new ForegroundPiece
|
||||
{
|
||||
AccentColour = { BindTarget = AccentColour },
|
||||
IsHitting = { BindTarget = IsHitting }
|
||||
};
|
||||
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent) => Background.Colour = accent.NewValue.Opacity(0.7f);
|
||||
|
||||
private class ForegroundPiece : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||
public readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||
|
||||
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
private BufferedContainer foregroundBuffer;
|
||||
private BufferedContainer subtractionBuffer;
|
||||
private Container subtractionLayer;
|
||||
|
||||
public ForegroundPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddLayout(subtractionCache);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = foregroundBuffer = new BufferedContainer
|
||||
{
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -53,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box { RelativeSizeAxes = Axes.Both },
|
||||
subtractionContainer = new BufferedContainer
|
||||
subtractionBuffer = new BufferedContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// This is needed because we're blending with another object
|
||||
@ -77,60 +117,51 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (drawableObject != null)
|
||||
{
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
AccentColour.BindTo(drawableObject.AccentColour);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||
isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent<Color4>(AccentColour.Value, AccentColour.Value)), true);
|
||||
}
|
||||
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||
{
|
||||
Foreground.Colour = accent.NewValue.Opacity(0.5f);
|
||||
Background.Colour = accent.NewValue.Opacity(0.7f);
|
||||
|
||||
const float animation_length = 50;
|
||||
|
||||
Foreground.ClearTransforms(false, nameof(Foreground.Colour));
|
||||
|
||||
if (isHitting.Value)
|
||||
{
|
||||
// wait for the next sync point
|
||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||
using (Foreground.BeginDelayedSequence(synchronisedOffset))
|
||||
Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
|
||||
}
|
||||
|
||||
subtractionCache.Invalidate();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!subtractionCache.IsValid)
|
||||
{
|
||||
subtractionLayer.Width = 5;
|
||||
subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth);
|
||||
subtractionLayer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = DrawWidth
|
||||
};
|
||||
|
||||
Foreground.ForceRedraw();
|
||||
subtractionContainer.ForceRedraw();
|
||||
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||
IsHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent<Color4>(AccentColour.Value, AccentColour.Value)), true);
|
||||
}
|
||||
|
||||
subtractionCache.Validate();
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||
{
|
||||
foregroundBuffer.Colour = accent.NewValue.Opacity(0.5f);
|
||||
|
||||
const float animation_length = 50;
|
||||
|
||||
foregroundBuffer.ClearTransforms(false, nameof(foregroundBuffer.Colour));
|
||||
|
||||
if (IsHitting.Value)
|
||||
{
|
||||
// wait for the next sync point
|
||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||
using (foregroundBuffer.BeginDelayedSequence(synchronisedOffset))
|
||||
foregroundBuffer.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(foregroundBuffer.Colour, animation_length).Loop();
|
||||
}
|
||||
|
||||
subtractionCache.Invalidate();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!subtractionCache.IsValid)
|
||||
{
|
||||
subtractionLayer.Width = 5;
|
||||
subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth);
|
||||
subtractionLayer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = DrawWidth
|
||||
};
|
||||
|
||||
foregroundBuffer.ForceRedraw();
|
||||
subtractionBuffer.ForceRedraw();
|
||||
|
||||
subtractionCache.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
// 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.Mania.Objects.Drawables.Pieces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for mania hold note bodies.
|
||||
/// </summary>
|
||||
public interface IHoldNoteBody
|
||||
{
|
||||
/// <summary>
|
||||
/// Recycles the contents of this <see cref="IHoldNoteBody"/> to free used resources.
|
||||
/// </summary>
|
||||
void Recycle();
|
||||
}
|
||||
}
|
@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column,
|
||||
Samples = getNodeSamples(0),
|
||||
Samples = GetNodeSamples(0),
|
||||
});
|
||||
|
||||
AddNested(Tail = new TailNote
|
||||
{
|
||||
StartTime = EndTime,
|
||||
Column = Column,
|
||||
Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||
});
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
||||
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) =>
|
||||
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
||||
}
|
||||
}
|
||||
|
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
public class HitTargetInsetContainer : Container
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private float hitPosition;
|
||||
|
||||
public HitTargetInsetContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||
{
|
||||
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
content.Padding = direction.NewValue == ScrollingDirection.Up
|
||||
? new MarginPadding { Top = hitPosition }
|
||||
: new MarginPadding { Bottom = hitPosition };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,7 +21,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||
|
||||
private Drawable sprite;
|
||||
[CanBeNull]
|
||||
private Drawable bodySprite;
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable lightContainer;
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable light;
|
||||
|
||||
public LegacyBodyPiece()
|
||||
{
|
||||
@ -32,7 +41,39 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||
?? $"mania-note{FallbackColumnIndex}L";
|
||||
|
||||
sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
string lightImage = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value
|
||||
?? "lightingL";
|
||||
|
||||
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||
?? 1;
|
||||
|
||||
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||
double frameLength = 0;
|
||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||
|
||||
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Origin = Anchor.Centre;
|
||||
d.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(lightScale);
|
||||
});
|
||||
|
||||
if (light != null)
|
||||
{
|
||||
lightContainer = new HitTargetInsetContainer
|
||||
{
|
||||
Alpha = 0,
|
||||
Child = light
|
||||
};
|
||||
}
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
@ -47,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
// Todo: Wrap
|
||||
});
|
||||
|
||||
if (sprite != null)
|
||||
InternalChild = sprite;
|
||||
if (bodySprite != null)
|
||||
InternalChild = bodySprite;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
@ -60,28 +101,68 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||
{
|
||||
if (!(sprite is TextureAnimation animation))
|
||||
if (bodySprite is TextureAnimation bodyAnimation)
|
||||
{
|
||||
bodyAnimation.GotoFrame(0);
|
||||
bodyAnimation.IsPlaying = isHitting.NewValue;
|
||||
}
|
||||
|
||||
if (lightContainer == null)
|
||||
return;
|
||||
|
||||
animation.GotoFrame(0);
|
||||
animation.IsPlaying = isHitting.NewValue;
|
||||
if (isHitting.NewValue)
|
||||
{
|
||||
// Clear the fade out and, more importantly, the removal.
|
||||
lightContainer.ClearTransforms();
|
||||
|
||||
// Only add the container if the removal has taken place.
|
||||
if (lightContainer.Parent == null)
|
||||
Column.TopLevelContainer.Add(lightContainer);
|
||||
|
||||
// The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847).
|
||||
if (light is TextureAnimation lightAnimation)
|
||||
lightAnimation.GotoFrame(0);
|
||||
|
||||
lightContainer.FadeIn(80);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightContainer.FadeOut(120)
|
||||
.OnComplete(d => Column.TopLevelContainer.Remove(d));
|
||||
}
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
if (sprite == null)
|
||||
return;
|
||||
|
||||
if (direction.NewValue == ScrollingDirection.Up)
|
||||
{
|
||||
sprite.Origin = Anchor.BottomCentre;
|
||||
sprite.Scale = new Vector2(1, -1);
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(1, -1);
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
light.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.Origin = Anchor.TopCentre;
|
||||
sprite.Scale = Vector2.One;
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = Vector2.One;
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
light.Anchor = Anchor.BottomCentre;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
lightContainer?.Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -18,14 +17,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly bool isLastColumn;
|
||||
|
||||
private Container lightContainer;
|
||||
private Sprite light;
|
||||
|
||||
public LegacyColumnBackground(bool isLastColumn)
|
||||
public LegacyColumnBackground()
|
||||
{
|
||||
this.isLastColumn = isLastColumn;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
@ -35,52 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
string lightImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LightImage)?.Value
|
||||
?? "mania-stage-light";
|
||||
|
||||
float leftLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
|
||||
?.Value ?? 1;
|
||||
float rightLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
|
||||
?.Value ?? 1;
|
||||
|
||||
bool hasLeftLine = leftLineWidth > 0;
|
||||
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
||||
|| isLastColumn;
|
||||
|
||||
float lightPosition = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
|
||||
?? 0;
|
||||
|
||||
Color4 lineColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
|
||||
?? Color4.White;
|
||||
|
||||
Color4 backgroundColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
|
||||
?? Color4.Black;
|
||||
|
||||
Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
|
||||
?? Color4.White;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = backgroundColour
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = leftLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasLeftLine ? 1 : 0
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = rightLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasRightLine ? 1 : 0
|
||||
},
|
||||
lightContainer = new Container
|
||||
{
|
||||
Origin = Anchor.BottomCentre,
|
||||
@ -90,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Colour = lightColour,
|
||||
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour),
|
||||
Texture = skin.GetTexture(lightImage),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
@ -66,6 +67,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
public void Animate(JudgementResult result)
|
||||
{
|
||||
if (result.Judgement is HoldNoteTickJudgement)
|
||||
return;
|
||||
|
||||
(explosion as IFramedAnimation)?.GotoFrame(0);
|
||||
|
||||
explosion?.FadeInFromZero(80)
|
||||
|
@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
private Container directionContainer;
|
||||
|
||||
public LegacyHitTarget()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||
{
|
||||
@ -56,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
Anchor = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 1,
|
||||
Colour = lineColour,
|
||||
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour),
|
||||
Alpha = showJudgementLine ? 0.9f : 0
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
if (GetColumnSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false)
|
||||
Column.UnderlayElements.Add(CreateProxy());
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
|
@ -4,19 +4,27 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
public class LegacyStageBackground : CompositeDrawable
|
||||
{
|
||||
private readonly StageDefinition stageDefinition;
|
||||
|
||||
private Drawable leftSprite;
|
||||
private Drawable rightSprite;
|
||||
private ColumnFlow<Drawable> columnBackgrounds;
|
||||
|
||||
public LegacyStageBackground()
|
||||
public LegacyStageBackground(StageDefinition stageDefinition)
|
||||
{
|
||||
this.stageDefinition = stageDefinition;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
@ -44,8 +52,19 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
Origin = Anchor.TopLeft,
|
||||
X = -0.05f,
|
||||
Texture = skin.GetTexture(rightImage)
|
||||
},
|
||||
columnBackgrounds = new ColumnFlow<Drawable>(stageDefinition)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y
|
||||
},
|
||||
new HitTargetInsetContainer
|
||||
{
|
||||
Child = new LegacyHitTarget { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||
columnBackgrounds.SetContentForColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1));
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -58,5 +77,72 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
if (rightSprite?.Height > 0)
|
||||
rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
|
||||
}
|
||||
|
||||
private class ColumnBackground : CompositeDrawable
|
||||
{
|
||||
private readonly int columnIndex;
|
||||
private readonly bool isLastColumn;
|
||||
|
||||
public ColumnBackground(int columnIndex, bool isLastColumn)
|
||||
{
|
||||
this.columnIndex = columnIndex;
|
||||
this.isLastColumn = isLastColumn;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
float leftLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1;
|
||||
float rightLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1;
|
||||
|
||||
bool hasLeftLine = leftLineWidth > 0;
|
||||
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
||||
|| isLastColumn;
|
||||
|
||||
Color4 lineColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White;
|
||||
Color4 backgroundColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, backgroundColour),
|
||||
new HitTargetInsetContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = leftLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Alpha = hasLeftLine ? 1 : 0,
|
||||
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, lineColour)
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = rightLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Alpha = hasRightLine ? 1 : 0,
|
||||
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}, lineColour)
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Skinning;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
@ -88,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
switch (maniaComponent.Component)
|
||||
{
|
||||
case ManiaSkinComponents.ColumnBackground:
|
||||
return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1);
|
||||
return new LegacyColumnBackground();
|
||||
|
||||
case ManiaSkinComponents.HitTarget:
|
||||
return new LegacyHitTarget();
|
||||
// Legacy skins sandwich the hit target between the column background and the column light.
|
||||
// To preserve this ordering, it's created manually inside LegacyStageBackground.
|
||||
return Drawable.Empty();
|
||||
|
||||
case ManiaSkinComponents.KeyArea:
|
||||
return new LegacyKeyArea();
|
||||
@ -112,7 +115,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
return new LegacyHitExplosion();
|
||||
|
||||
case ManiaSkinComponents.StageBackground:
|
||||
return new LegacyStageBackground();
|
||||
Debug.Assert(maniaComponent.StageDefinition != null);
|
||||
return new LegacyStageBackground(maniaComponent.StageDefinition.Value);
|
||||
|
||||
case ManiaSkinComponents.StageForeground:
|
||||
return new LegacyStageForeground();
|
||||
|
@ -68,8 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||
}
|
||||
|
||||
public override Axes RelativeSizeAxes => Axes.Y;
|
||||
|
||||
public ColumnType ColumnType { get; set; }
|
||||
|
||||
public bool IsSpecial => ColumnType == ColumnType.Special;
|
||||
|
105
osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
Normal file
105
osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Drawable"/> which flows its contents according to the <see cref="Column"/>s in a <see cref="Stage"/>.
|
||||
/// Content can be added to individual columns via <see cref="SetContentForColumn"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContent">The type of content in each column.</typeparam>
|
||||
public class ColumnFlow<TContent> : CompositeDrawable
|
||||
where TContent : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// All contents added to this <see cref="ColumnFlow{TContent}"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TContent> Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList();
|
||||
|
||||
private readonly FillFlowContainer<Container> columns;
|
||||
private readonly StageDefinition stageDefinition;
|
||||
|
||||
public ColumnFlow(StageDefinition stageDefinition)
|
||||
{
|
||||
this.stageDefinition = stageDefinition;
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
|
||||
InternalChild = columns = new FillFlowContainer<Container>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
};
|
||||
|
||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||
columns.Add(new Container { RelativeSizeAxes = Axes.Y });
|
||||
}
|
||||
|
||||
private ISkinSource currentSkin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
currentSkin = skin;
|
||||
|
||||
skin.SourceChanged += onSkinChanged;
|
||||
onSkinChanged();
|
||||
}
|
||||
|
||||
private void onSkinChanged()
|
||||
{
|
||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1))
|
||||
?.Value ?? Stage.COLUMN_SPACING;
|
||||
|
||||
columns[i].Margin = new MarginPadding { Left = spacing };
|
||||
}
|
||||
|
||||
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
|
||||
?.Value;
|
||||
|
||||
if (width == null)
|
||||
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
|
||||
columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
|
||||
else
|
||||
columns[i].Width = width.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of one of the columns of this <see cref="ColumnFlow{TContent}"/>.
|
||||
/// </summary>
|
||||
/// <param name="column">The index of the column to set the content of.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public void SetContentForColumn(int column, TContent content) => columns[column].Child = content;
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (currentSkin != null)
|
||||
currentSkin.SourceChanged -= onSkinChanged;
|
||||
}
|
||||
}
|
||||
}
|
@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// The minimum time range. This occurs at a <see cref="relativeTimeRange"/> of 40.
|
||||
/// </summary>
|
||||
public const double MIN_TIME_RANGE = 150;
|
||||
public const double MIN_TIME_RANGE = 340;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time range. This occurs at a <see cref="relativeTimeRange"/> of 1.
|
||||
/// </summary>
|
||||
public const double MAX_TIME_RANGE = 6000;
|
||||
public const double MAX_TIME_RANGE = 13720;
|
||||
|
||||
protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield;
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
@ -11,7 +10,6 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -31,14 +29,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public const float HIT_TARGET_POSITION = 110;
|
||||
|
||||
public IReadOnlyList<Column> Columns => columnFlow.Children;
|
||||
private readonly FillFlowContainer<Column> columnFlow;
|
||||
public IReadOnlyList<Column> Columns => columnFlow.Content;
|
||||
private readonly ColumnFlow<Column> columnFlow;
|
||||
|
||||
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
|
||||
private readonly DrawablePool<DrawableManiaJudgement> judgementPool;
|
||||
|
||||
private readonly Drawable barLineContainer;
|
||||
private readonly Container topLevelContainer;
|
||||
|
||||
private readonly Dictionary<ColumnType, Color4> columnColours = new Dictionary<ColumnType, Color4>
|
||||
{
|
||||
@ -62,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
AutoSizeAxes = Axes.X;
|
||||
|
||||
Container topLevelContainer;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
judgementPool = new DrawablePool<DrawableManiaJudgement>(2),
|
||||
@ -73,17 +72,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
AutoSizeAxes = Axes.X,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
|
||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
columnFlow = new FillFlowContainer<Column>
|
||||
columnFlow = new ColumnFlow<Column>(definition)
|
||||
{
|
||||
Name = "Columns",
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -102,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
}
|
||||
},
|
||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
|
||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
@ -121,60 +116,22 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
for (int i = 0; i < definition.Columns; i++)
|
||||
{
|
||||
var columnType = definition.GetTypeOfColumn(i);
|
||||
|
||||
var column = new Column(firstColumnIndex + i)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 1,
|
||||
ColumnType = columnType,
|
||||
AccentColour = columnColours[columnType],
|
||||
Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
|
||||
};
|
||||
|
||||
AddColumn(column);
|
||||
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
||||
columnFlow.SetContentForColumn(i, column);
|
||||
AddNested(column);
|
||||
}
|
||||
}
|
||||
|
||||
private ISkin currentSkin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
currentSkin = skin;
|
||||
skin.SourceChanged += onSkinChanged;
|
||||
|
||||
onSkinChanged();
|
||||
}
|
||||
|
||||
private void onSkinChanged()
|
||||
{
|
||||
foreach (var col in columnFlow)
|
||||
{
|
||||
if (col.Index > 0)
|
||||
{
|
||||
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1))
|
||||
?.Value ?? COLUMN_SPACING;
|
||||
|
||||
col.Margin = new MarginPadding { Left = spacing };
|
||||
}
|
||||
|
||||
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index))
|
||||
?.Value;
|
||||
|
||||
if (width == null)
|
||||
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
|
||||
col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
|
||||
else
|
||||
col.Width = width.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddColumn(Column c)
|
||||
{
|
||||
topLevelContainer.Add(c.TopLevelContainer.CreateProxy());
|
||||
columnFlow.Add(c);
|
||||
AddNested(c);
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
{
|
||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
this.hasColours = hasColours;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
|
||||
protected override IBeatmapSkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
|
||||
}
|
||||
|
||||
private class TestBeatmapSkin : LegacyBeatmapSkin
|
||||
|
@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public class TestSceneSkinFallbacks : TestSceneOsuPlayer
|
||||
{
|
||||
private readonly TestSource testUserSkin;
|
||||
private readonly TestSource testBeatmapSkin;
|
||||
private readonly BeatmapTestSource testBeatmapSkin;
|
||||
|
||||
public TestSceneSkinFallbacks()
|
||||
{
|
||||
testUserSkin = new TestSource("user");
|
||||
testBeatmapSkin = new TestSource("beatmap");
|
||||
testBeatmapSkin = new BeatmapTestSource();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -80,15 +80,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||
{
|
||||
private readonly ISkinSource skin;
|
||||
private readonly IBeatmapSkin skin;
|
||||
|
||||
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
|
||||
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, IBeatmapSkin skin)
|
||||
: base(beatmap, storyboard, frameBasedClock, audio)
|
||||
{
|
||||
this.skin = skin;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin() => skin;
|
||||
protected override IBeatmapSkin GetSkin() => skin;
|
||||
}
|
||||
|
||||
public class SkinProvidingPlayer : TestPlayer
|
||||
@ -112,6 +112,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private class BeatmapTestSource : TestSource, IBeatmapSkin
|
||||
{
|
||||
public BeatmapTestSource()
|
||||
: base("beatmap")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestSource : ISkinSource
|
||||
{
|
||||
private readonly string identifier;
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -62,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
drawableSpinner = new TestDrawableSpinner(spinner, auto)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Depth = depthIndex++
|
||||
Depth = depthIndex++,
|
||||
Scale = new Vector2(0.75f)
|
||||
};
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Skinning;
|
||||
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||
|
||||
private bool spinnerFrequencyModulate;
|
||||
|
||||
public DrawableSpinner(Spinner s)
|
||||
: base(s)
|
||||
{
|
||||
@ -82,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
|
||||
private SkinnableSound spinningSample;
|
||||
private const float spinning_sample_initial_frequency = 1.0f;
|
||||
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
@ -101,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
Volume = { Value = 0 },
|
||||
Looping = true,
|
||||
Frequency = { Value = spinning_sample_initial_frequency }
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -171,6 +177,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
}
|
||||
|
||||
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.ApplySkin(skin, allowFallback);
|
||||
spinnerFrequencyModulate = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerFrequencyModulate)?.Value ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The completion progress of this spinner from 0..1 (clamped).
|
||||
/// </summary>
|
||||
@ -220,9 +232,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (HandleUserInput)
|
||||
RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
|
||||
if (spinningSample != null)
|
||||
// todo: implement SpinnerFrequencyModulate
|
||||
spinningSample.Frequency.Value = 0.5f + Progress;
|
||||
if (spinningSample != null && spinnerFrequencyModulate)
|
||||
spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
|
||||
{
|
||||
protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
|
||||
protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0;
|
||||
|
||||
protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public override bool AffectsCombo => false;
|
||||
|
||||
protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
|
||||
protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0;
|
||||
|
||||
protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0;
|
||||
}
|
||||
|
@ -193,30 +193,46 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
new StatisticRow
|
||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||
|
||||
return new[]
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList())
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticItem("Timing Distribution",
|
||||
new HitEventTimingDistributionGraph(timedHitEvents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(timedHitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
hitCircleSprite = new Sprite
|
||||
{
|
||||
Texture = getTextureWithFallback(string.Empty),
|
||||
Colour = drawableObject.AccentColour.Value,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
private DrawableSpinner drawableSpinner;
|
||||
private Sprite disc;
|
||||
private Sprite metreSprite;
|
||||
private Container metre;
|
||||
|
||||
private const float background_y_offset = 20;
|
||||
|
||||
private const float sprite_scale = 1 / 1.6f;
|
||||
private const float final_metre_height = 692 * sprite_scale;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource source, DrawableHitObject drawableObject)
|
||||
@ -35,50 +35,58 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = new Container
|
||||
{
|
||||
new Sprite
|
||||
// the old-style spinner relied heavily on absolute screen-space coordinate values.
|
||||
// wrap everything in a container simulating absolute coords to preserve alignment
|
||||
// as there are skins that depend on it.
|
||||
Width = 640,
|
||||
Height = 480,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Texture = source.GetTexture("spinner-background"),
|
||||
Y = background_y_offset,
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
disc = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-circle"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
metre = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Y = background_y_offset,
|
||||
Masking = true,
|
||||
Child = new Sprite
|
||||
new Sprite
|
||||
{
|
||||
Texture = source.GetTexture("spinner-metre"),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-background"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
Scale = new Vector2(0.625f)
|
||||
disc = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-circle"),
|
||||
Scale = new Vector2(sprite_scale)
|
||||
},
|
||||
metre = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
// this anchor makes no sense, but that's what stable uses.
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
// adjustment for stable (metre has additional offset)
|
||||
Margin = new MarginPadding { Top = 20 },
|
||||
Masking = true,
|
||||
Child = metreSprite = new Sprite
|
||||
{
|
||||
Texture = source.GetTexture("spinner-metre"),
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Scale = new Vector2(0.625f)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Vector2 metreFinalSize;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
this.FadeOut();
|
||||
drawableSpinner.State.BindValueChanged(updateStateTransforms, true);
|
||||
|
||||
metreFinalSize = metre.Size = metre.Child.Size;
|
||||
}
|
||||
|
||||
private void updateStateTransforms(ValueChangedEvent<ArmedState> state)
|
||||
@ -93,7 +101,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
base.Update();
|
||||
disc.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||
metre.Height = getMetreHeight(drawableSpinner.Progress);
|
||||
|
||||
// careful: need to call this exactly once for all calculations in a frame
|
||||
// as the function has a random factor in it
|
||||
var metreHeight = getMetreHeight(drawableSpinner.Progress);
|
||||
|
||||
// hack to make the metre blink up from below than down from above.
|
||||
// move down the container to be able to apply masking for the metre,
|
||||
// and then move the sprite back up the same amount to keep its position absolute.
|
||||
metre.Y = final_metre_height - metreHeight;
|
||||
metreSprite.Y = -metre.Y;
|
||||
}
|
||||
|
||||
private const int total_bars = 10;
|
||||
@ -108,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
if (RNG.NextBool(((int)progress % 10) / 10f))
|
||||
barCount++;
|
||||
|
||||
return (float)barCount / total_bars * metreFinalSize.Y;
|
||||
return (float)barCount / total_bars * final_metre_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, DrawableHitObject drawableObject)
|
||||
{
|
||||
animationContent.Colour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
|
||||
var ballColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
@ -39,11 +39,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
Texture = skin.GetTexture("sliderb-nd"),
|
||||
Colour = new Color4(5, 5, 5, 255),
|
||||
},
|
||||
animationContent.With(d =>
|
||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
}),
|
||||
}), ballColour),
|
||||
layerSpec = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
CursorExpand,
|
||||
CursorRotate,
|
||||
HitCircleOverlayAboveNumber,
|
||||
HitCircleOverlayAboveNumer // Some old skins will have this typo
|
||||
HitCircleOverlayAboveNumer, // Some old skins will have this typo
|
||||
SpinnerFrequencyModulate
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
backgroundLayer.Colour = accentColour;
|
||||
backgroundLayer.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
headCircle.AccentColour = accentColour;
|
||||
body.Colour = accentColour;
|
||||
end.Colour = accentColour;
|
||||
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
|
||||
|
||||
headCircle.AccentColour = colour;
|
||||
body.Colour = colour;
|
||||
end.Colour = colour;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
@ -18,9 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AccentColour = component == TaikoSkinComponents.CentreHit
|
||||
? new Color4(235, 69, 44, 255)
|
||||
: new Color4(67, 142, 172, 255);
|
||||
AccentColour = LegacyColourCompatibility.DisallowZeroAlpha(
|
||||
component == TaikoSkinComponents.CentreHit
|
||||
? new Color4(235, 69, 44, 255)
|
||||
: new Color4(67, 142, 172, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,19 +161,34 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
new StatisticRow
|
||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||
|
||||
return new[]
|
||||
{
|
||||
Columns = new[]
|
||||
new StatisticRow
|
||||
{
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList())
|
||||
Columns = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250
|
||||
}),
|
||||
}
|
||||
},
|
||||
new StatisticRow
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||
{
|
||||
new UnstableRate(timedHitEvents)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
Normal file
32
osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[TestFixture]
|
||||
public class BeatmapDifficultyManagerTest
|
||||
{
|
||||
[Test]
|
||||
public void TestKeyEqualsWithDifferentModInstances()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
|
||||
Assert.That(key1, Is.EqualTo(key2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyEqualsWithDifferentModOrder()
|
||||
{
|
||||
var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||
var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
|
||||
|
||||
Assert.That(key1, Is.EqualTo(key2));
|
||||
}
|
||||
}
|
||||
}
|
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
[TestFixture]
|
||||
public class LegacyScoreDecoderTest
|
||||
{
|
||||
[Test]
|
||||
public void TestDecodeManiaReplay()
|
||||
{
|
||||
var decoder = new TestLegacyScoreDecoder();
|
||||
|
||||
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||
{
|
||||
var score = decoder.Parse(resourceStream);
|
||||
|
||||
Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID);
|
||||
|
||||
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||
|
||||
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
|
||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
|
||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||
|
||||
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
||||
{
|
||||
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
||||
{
|
||||
new OsuRuleset(),
|
||||
new TaikoRuleset(),
|
||||
new CatchRuleset(),
|
||||
new ManiaRuleset()
|
||||
}.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
|
||||
|
||||
protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
|
||||
|
||||
protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
MD5Hash = md5Hash,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BaseDifficulty = new BeatmapDifficulty()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -169,17 +169,17 @@ namespace osu.Game.Tests.Editing
|
||||
[Test]
|
||||
public void GetSnappedDistanceFromDistance()
|
||||
{
|
||||
assertSnappedDistance(50, 100);
|
||||
assertSnappedDistance(50, 0);
|
||||
assertSnappedDistance(100, 100);
|
||||
assertSnappedDistance(150, 200);
|
||||
assertSnappedDistance(150, 100);
|
||||
assertSnappedDistance(200, 200);
|
||||
assertSnappedDistance(250, 300);
|
||||
assertSnappedDistance(250, 200);
|
||||
|
||||
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
|
||||
|
||||
assertSnappedDistance(50, 0);
|
||||
assertSnappedDistance(100, 200);
|
||||
assertSnappedDistance(150, 200);
|
||||
assertSnappedDistance(100, 0);
|
||||
assertSnappedDistance(150, 0);
|
||||
assertSnappedDistance(200, 200);
|
||||
assertSnappedDistance(250, 200);
|
||||
|
||||
@ -190,8 +190,8 @@ namespace osu.Game.Tests.Editing
|
||||
});
|
||||
|
||||
assertSnappedDistance(50, 0);
|
||||
assertSnappedDistance(100, 200);
|
||||
assertSnappedDistance(150, 200);
|
||||
assertSnappedDistance(100, 0);
|
||||
assertSnappedDistance(150, 0);
|
||||
assertSnappedDistance(200, 200);
|
||||
assertSnappedDistance(250, 200);
|
||||
assertSnappedDistance(400, 400);
|
||||
|
41
osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
Normal file
41
osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneScoreProcessor : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestNoScoreIncreaseFromMiss()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject> { HitObjects = { new TestHitObject() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
// Apply a miss judgement
|
||||
scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss });
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
|
||||
}
|
||||
|
||||
private class TestHitObject : HitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new TestJudgement();
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
protected override int NumericResultFor(HitResult result) => 100;
|
||||
}
|
||||
}
|
||||
}
|
@ -116,7 +116,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate);
|
||||
}
|
||||
|
||||
private class TestSkin : LegacySkin
|
||||
private class TestSkin : LegacySkin, IBeatmapSkin
|
||||
{
|
||||
public TestSkin(string resourceName, AudioManager audioManager)
|
||||
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
this.audio = audio;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin() => new TestSkin("test-sample", audio);
|
||||
protected override IBeatmapSkin GetSkin() => new TestSkin("test-sample", audio);
|
||||
}
|
||||
|
||||
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyDrainRateQueries()
|
||||
public void TestApplyDrainRateQueriesByDrKeyword()
|
||||
{
|
||||
const string query = "dr>2 quite specific dr<:6";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyDrainRateQueriesByHpKeyword()
|
||||
{
|
||||
const string query = "hp>2 quite specific hp<=6";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim());
|
||||
Assert.AreEqual(2, filterCriteria.SearchTerms.Length);
|
||||
Assert.Greater(filterCriteria.DrainRate.Min, 2.0f);
|
||||
Assert.Less(filterCriteria.DrainRate.Min, 2.1f);
|
||||
Assert.Greater(filterCriteria.DrainRate.Max, 6.0f);
|
||||
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyBPMQueries()
|
||||
{
|
||||
|
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
Binary file not shown.
@ -1,5 +0,0 @@
|
||||
[General]
|
||||
Version: latest
|
||||
|
||||
[Colours]
|
||||
Combo1: 255,255,255,0
|
@ -108,15 +108,5 @@ namespace osu.Game.Tests.Skins
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeColourWithZeroAlpha()
|
||||
{
|
||||
var decoder = new LegacySkinDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
||||
AddStep("exit loader", () => loader.Exit());
|
||||
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
||||
AddAssert("player did not load", () => player == null);
|
||||
|
@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
TextSize = 40,
|
||||
Margin = new MarginPadding(20),
|
||||
};
|
||||
Add(score);
|
||||
@ -30,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding(10),
|
||||
TextSize = 40,
|
||||
};
|
||||
Add(comboCounter);
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK.Input;
|
||||
@ -15,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[TestFixture]
|
||||
public class TestSceneToolbar : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Toolbar toolbar;
|
||||
private TestToolbar toolbar;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
@ -23,7 +25,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } };
|
||||
Child = toolbar = new TestToolbar { State = { Value = Visibility.Visible } };
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -72,5 +74,24 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(OverlayActivation.All)]
|
||||
[TestCase(OverlayActivation.Disabled)]
|
||||
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
||||
{
|
||||
AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
|
||||
AddStep("hide toolbar", () => toolbar.Hide());
|
||||
AddStep("try to show toolbar", () => toolbar.Show());
|
||||
|
||||
if (mode == OverlayActivation.Disabled)
|
||||
AddAssert("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden);
|
||||
else
|
||||
AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
public class TestToolbar : Toolbar
|
||||
{
|
||||
public new Bindable<OverlayActivation> OverlayActivationMode => base.OverlayActivationMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -10,11 +12,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController { get; set; } = new MusicController();
|
||||
|
||||
public TestSceneMultiScreen()
|
||||
{
|
||||
Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer();
|
||||
|
||||
AddStep(@"show", () => LoadScreen(multi));
|
||||
AddStep("show", () => LoadScreen(multi));
|
||||
AddUntilStep("wait for loaded", () => multi.IsLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,10 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Screens.Select.Details;
|
||||
@ -72,6 +74,32 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlyFailMetrics()
|
||||
{
|
||||
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
|
||||
{
|
||||
Metrics = new BeatmapMetrics
|
||||
{
|
||||
Fails = Enumerable.Range(1, 100).ToArray(),
|
||||
}
|
||||
});
|
||||
AddAssert("graph max values correct",
|
||||
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyMetrics()
|
||||
{
|
||||
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
|
||||
{
|
||||
Metrics = new BeatmapMetrics()
|
||||
});
|
||||
|
||||
AddAssert("graph max values correct",
|
||||
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 0));
|
||||
}
|
||||
|
||||
private class GraphExposingSuccessRate : SuccessRate
|
||||
{
|
||||
public new FailRetryGraph Graph => base.Graph;
|
||||
|
@ -0,0 +1,68 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneSimpleStatisticTable : OsuTestScene
|
||||
{
|
||||
private Container container;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 700,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#333"),
|
||||
},
|
||||
container = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(20)
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmpty()
|
||||
{
|
||||
AddStep("create with no items",
|
||||
() => container.Add(new SimpleStatisticTable(2, Enumerable.Empty<SimpleStatisticItem>())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyItems(
|
||||
[Values(1, 2, 3, 4, 12)] int itemCount,
|
||||
[Values(1, 3, 5)] int columnCount)
|
||||
{
|
||||
AddStep($"create with {"item".ToQuantity(itemCount)}", () =>
|
||||
{
|
||||
var items = Enumerable.Range(1, itemCount)
|
||||
.Select(i => new SimpleStatisticItem<int>($"Statistic #{i}")
|
||||
{
|
||||
Value = RNG.Next(100)
|
||||
});
|
||||
|
||||
container.Add(new SimpleStatisticTable(columnCount, items));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -68,22 +68,22 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
[Test]
|
||||
public void TestClearButtonOnBindings()
|
||||
{
|
||||
KeyBindingRow backBindingRow = null;
|
||||
KeyBindingRow multiBindingRow = null;
|
||||
|
||||
AddStep("click back binding row", () =>
|
||||
AddStep("click first row with two bindings", () =>
|
||||
{
|
||||
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||
InputManager.MoveMouseTo(backBindingRow);
|
||||
multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().First(row => row.Defaults.Count() > 1);
|
||||
InputManager.MoveMouseTo(multiBindingRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
clickClearButton();
|
||||
|
||||
AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text));
|
||||
AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text));
|
||||
|
||||
AddStep("click second binding", () =>
|
||||
{
|
||||
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||
var target = multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||
|
||||
InputManager.MoveMouseTo(target);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
clickClearButton();
|
||||
|
||||
AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text));
|
||||
AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text));
|
||||
|
||||
void clickClearButton()
|
||||
{
|
||||
AddStep("click clear button", () =>
|
||||
{
|
||||
var clearButton = backBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||
var clearButton = multiBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(clearButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
@ -108,20 +108,20 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
[Test]
|
||||
public void TestClickRowSelectsFirstBinding()
|
||||
{
|
||||
KeyBindingRow backBindingRow = null;
|
||||
KeyBindingRow multiBindingRow = null;
|
||||
|
||||
AddStep("click back binding row", () =>
|
||||
AddStep("click first row with two bindings", () =>
|
||||
{
|
||||
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||
InputManager.MoveMouseTo(backBindingRow);
|
||||
multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().First(row => row.Defaults.Count() > 1);
|
||||
InputManager.MoveMouseTo(multiBindingRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||
|
||||
AddStep("click second binding", () =>
|
||||
{
|
||||
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||
var target = multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||
|
||||
InputManager.MoveMouseTo(target);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
@ -129,12 +129,12 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
AddStep("click back binding row", () =>
|
||||
{
|
||||
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||
InputManager.MoveMouseTo(backBindingRow);
|
||||
multiBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||
InputManager.MoveMouseTo(multiBindingRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Tournament.IPC;
|
||||
using osu.Game.Tournament.Models;
|
||||
@ -127,21 +128,29 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
|
||||
private class MatchScoreCounter : ScoreCounter
|
||||
{
|
||||
private OsuSpriteText displayedSpriteText;
|
||||
|
||||
public MatchScoreCounter()
|
||||
{
|
||||
Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
|
||||
|
||||
Winning = false;
|
||||
|
||||
DisplayedCountSpriteText.Spacing = new Vector2(-6);
|
||||
}
|
||||
|
||||
public bool Winning
|
||||
{
|
||||
set => DisplayedCountSpriteText.Font = value
|
||||
set => updateFont(value);
|
||||
}
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
|
||||
{
|
||||
displayedSpriteText = s;
|
||||
displayedSpriteText.Spacing = new Vector2(-6);
|
||||
updateFont(false);
|
||||
});
|
||||
|
||||
private void updateFont(bool winning)
|
||||
=> displayedSpriteText.Font = winning
|
||||
? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true)
|
||||
: OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Tournament
|
||||
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
|
||||
private Drawable heightWarning;
|
||||
private Bindable<Size> windowSize;
|
||||
private Bindable<WindowMode> windowMode;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkConfigManager frameworkConfig)
|
||||
@ -43,6 +44,12 @@ namespace osu.Game.Tournament
|
||||
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
|
||||
}), true);
|
||||
|
||||
windowMode = frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
||||
windowMode.BindValueChanged(mode => ScheduleAfterChildren(() =>
|
||||
{
|
||||
windowMode.Value = WindowMode.Windowed;
|
||||
}), true);
|
||||
|
||||
AddRange(new[]
|
||||
{
|
||||
new Container
|
||||
|
@ -89,8 +89,14 @@ namespace osu.Game.Beatmaps
|
||||
if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
|
||||
return existing;
|
||||
|
||||
return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken,
|
||||
TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
// Computation may have finished in a previous task.
|
||||
if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out existing, out _))
|
||||
return existing;
|
||||
|
||||
return computeDifficulty(key, beatmapInfo, rulesetInfo);
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -245,7 +251,7 @@ namespace osu.Game.Beatmaps
|
||||
updateScheduler?.Dispose();
|
||||
}
|
||||
|
||||
private readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup>
|
||||
public readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup>
|
||||
{
|
||||
public readonly int BeatmapId;
|
||||
public readonly int RulesetId;
|
||||
@ -261,7 +267,7 @@ namespace osu.Game.Beatmaps
|
||||
public bool Equals(DifficultyCacheLookup other)
|
||||
=> BeatmapId == other.BeatmapId
|
||||
&& RulesetId == other.RulesetId
|
||||
&& Mods.SequenceEqual(other.Mods);
|
||||
&& Mods.Select(m => m.Acronym).SequenceEqual(other.Mods.Select(m => m.Acronym));
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
|
@ -140,7 +140,7 @@ namespace osu.Game.Beatmaps
|
||||
return storyboard;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin()
|
||||
protected override IBeatmapSkin GetSkin()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -104,10 +104,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
try
|
||||
{
|
||||
byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
|
||||
|
||||
if (alpha == 0)
|
||||
alpha = 255;
|
||||
|
||||
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
|
||||
}
|
||||
catch
|
||||
|
@ -42,9 +42,9 @@ namespace osu.Game.Beatmaps
|
||||
Storyboard Storyboard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="Skin"/> which this <see cref="WorkingBeatmap"/> provides.
|
||||
/// Retrieves the <see cref="IBeatmapSkin"/> which this <see cref="WorkingBeatmap"/> provides.
|
||||
/// </summary>
|
||||
ISkin Skin { get; }
|
||||
IBeatmapSkin Skin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user