mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 05:22:54 +08:00
Merge branch 'master' into shift-taiko-playfield
This commit is contained in:
commit
afc4b63473
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -16,15 +16,14 @@
|
|||||||
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
||||||
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
||||||
"osu.Game\\osu.Game.csproj",
|
"osu.Game\\osu.Game.csproj",
|
||||||
|
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -102,7 +102,7 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null }))
|
||||||
{
|
{
|
||||||
if (!host.IsPrimaryInstance)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.10.2" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
|
||||||
{
|
{
|
||||||
if (accuracy == accuracy_cutoff_x)
|
if (accuracy == accuracy_cutoff_x)
|
||||||
return ScoreRank.X;
|
return ScoreRank.X;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -16,37 +14,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMinor()
|
public void TestMinor()
|
||||||
{
|
{
|
||||||
AddStep("Create barlines", () => recreate());
|
AddStep("Create barlines", recreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recreate(Func<IEnumerable<BarLine>>? createBarLines = null)
|
private void recreate()
|
||||||
{
|
{
|
||||||
var stageDefinitions = new List<StageDefinition>
|
var stageDefinitions = new List<StageDefinition>
|
||||||
{
|
{
|
||||||
new StageDefinition(4),
|
new StageDefinition(4),
|
||||||
};
|
};
|
||||||
|
|
||||||
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
if (createBarLines != null)
|
var maniaPlayfield = new ManiaPlayfield(stageDefinitions);
|
||||||
|
|
||||||
|
// Must be scheduled so the pool is loaded before we try and retrieve from it.
|
||||||
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
var barLines = createBarLines();
|
for (int i = 0; i < 64; i++)
|
||||||
|
|
||||||
foreach (var b in barLines)
|
|
||||||
s.Add(b);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
{
|
|
||||||
s.Add(new BarLine
|
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + i * 500,
|
maniaPlayfield.Add(new BarLine
|
||||||
Major = i % 4 == 0,
|
{
|
||||||
});
|
StartTime = Time.Current + i * 500,
|
||||||
}
|
Major = i % 4 == 0,
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return maniaPlayfield;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -25,16 +25,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; } = null!;
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
private readonly List<DrawablePool<TestDrawableOsuJudgement>> pools;
|
private readonly List<DrawablePool<TestDrawableOsuJudgement>> pools = new List<DrawablePool<TestDrawableOsuJudgement>>();
|
||||||
|
|
||||||
public TestSceneDrawableJudgement()
|
[TestCaseSource(nameof(validResults))]
|
||||||
|
public void Test(HitResult result)
|
||||||
{
|
{
|
||||||
pools = new List<DrawablePool<TestDrawableOsuJudgement>>();
|
showResult(result);
|
||||||
|
|
||||||
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
|
|
||||||
showResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<HitResult> validResults => Enum.GetValues<HitResult>().Skip(1);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitLightingDisabled()
|
public void TestHitLightingDisabled()
|
||||||
{
|
{
|
||||||
@ -72,32 +72,33 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
pools.Add(pool = new DrawablePool<TestDrawableOsuJudgement>(1));
|
pools.Add(pool = new DrawablePool<TestDrawableOsuJudgement>(1));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pool = pools[poolIndex];
|
|
||||||
|
|
||||||
// We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
|
// We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
|
||||||
|
pool = pools[poolIndex];
|
||||||
((Container)pool.Parent!).Clear(false);
|
((Container)pool.Parent!).Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var container = new Container
|
var container = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Child = pool,
|
||||||
{
|
|
||||||
pool,
|
|
||||||
pool.Get(j => j.Apply(new JudgementResult(new HitObject
|
|
||||||
{
|
|
||||||
StartTime = Time.Current
|
|
||||||
}, new Judgement())
|
|
||||||
{
|
|
||||||
Type = result,
|
|
||||||
}, null)).With(j =>
|
|
||||||
{
|
|
||||||
j.Anchor = Anchor.Centre;
|
|
||||||
j.Origin = Anchor.Centre;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Must be scheduled so the pool is loaded before we try and retrieve from it.
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
container.Add(pool.Get(j => j.Apply(new JudgementResult(new HitObject
|
||||||
|
{
|
||||||
|
StartTime = Time.Current
|
||||||
|
}, new Judgement())
|
||||||
|
{
|
||||||
|
Type = result,
|
||||||
|
}, null)).With(j =>
|
||||||
|
{
|
||||||
|
j.Anchor = Anchor.Centre;
|
||||||
|
j.Origin = Anchor.Centre;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
poolIndex++;
|
poolIndex++;
|
||||||
return container;
|
return container;
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
DrawableSlider dho = null;
|
DrawableSlider dho = null;
|
||||||
|
|
||||||
AddStep("create slider", () => Child = dho = new DrawableSlider(prepareObject(new Slider
|
AddStep("create slider", () => Child = dho = new DrawableSlider(applyDefaults(new Slider
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 192),
|
Position = new Vector2(256, 192),
|
||||||
IndexInCurrentCombo = 0,
|
IndexInCurrentCombo = 0,
|
||||||
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddWaitStep("wait for progression", 1);
|
AddWaitStep("wait for progression", 1);
|
||||||
|
|
||||||
AddStep("apply new slider", () => dho.Apply(prepareObject(new Slider
|
AddStep("apply new slider", () => dho.Apply(applyDefaults(new Slider
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 192),
|
Position = new Vector2(256, 192),
|
||||||
ComboIndex = 1,
|
ComboIndex = 1,
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Child = new SkinProvidingContainer(provider)
|
Child = new SkinProvidingContainer(provider)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = dho = new DrawableSlider(prepareObject(new Slider
|
Child = dho = new DrawableSlider(applyDefaults(new Slider
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 192),
|
Position = new Vector2(256, 192),
|
||||||
IndexInCurrentCombo = 0,
|
IndexInCurrentCombo = 0,
|
||||||
@ -97,7 +97,38 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("ball is red", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.Red);
|
AddAssert("ball is red", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Slider prepareObject(Slider slider)
|
[Test]
|
||||||
|
public void TestIncreaseRepeatCount()
|
||||||
|
{
|
||||||
|
DrawableSlider dho = null;
|
||||||
|
|
||||||
|
AddStep("create slider", () =>
|
||||||
|
{
|
||||||
|
Child = dho = new DrawableSlider(applyDefaults(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
IndexInCurrentCombo = 0,
|
||||||
|
StartTime = Time.Current,
|
||||||
|
Path = new SliderPath(PathType.LINEAR, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(150, 100),
|
||||||
|
new Vector2(300, 0),
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("increase repeat count", () =>
|
||||||
|
{
|
||||||
|
dho.HitObject.RepeatCount++;
|
||||||
|
applyDefaults(dho.HitObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("repeat got custom anchor", () =>
|
||||||
|
dho.ChildrenOfType<DrawableSliderRepeat>().Single().RelativeAnchorPosition == Vector2.Divide(dho.SliderBody!.PathOffset, dho.DrawSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Slider applyDefaults(Slider slider)
|
||||||
{
|
{
|
||||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
return slider;
|
return slider;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="Moq" Version="4.18.4" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private Container<DrawableSliderRepeat> repeatContainer;
|
private Container<DrawableSliderRepeat> repeatContainer;
|
||||||
private PausableSkinnableSound slidingSample;
|
private PausableSkinnableSound slidingSample;
|
||||||
|
|
||||||
private readonly LayoutValue drawSizeLayout;
|
private readonly LayoutValue relativeAnchorPositionLayout;
|
||||||
|
|
||||||
public DrawableSlider()
|
public DrawableSlider()
|
||||||
: this(null)
|
: this(null)
|
||||||
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
};
|
};
|
||||||
AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry));
|
AddLayout(relativeAnchorPositionLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -190,6 +190,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
repeatContainer.Add(repeat);
|
repeatContainer.Add(repeat);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relativeAnchorPositionLayout.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
@ -265,14 +267,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Size = SliderBody?.Size ?? Vector2.Zero;
|
Size = SliderBody?.Size ?? Vector2.Zero;
|
||||||
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
|
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
|
||||||
|
|
||||||
if (!drawSizeLayout.IsValid)
|
if (!relativeAnchorPositionLayout.IsValid)
|
||||||
{
|
{
|
||||||
Vector2 pos = Vector2.Divide(OriginPosition, DrawSize);
|
Vector2 pos = Vector2.Divide(OriginPosition, DrawSize);
|
||||||
foreach (var obj in NestedHitObjects)
|
foreach (var obj in NestedHitObjects)
|
||||||
obj.RelativeAnchorPosition = pos;
|
obj.RelativeAnchorPosition = pos;
|
||||||
Ball.RelativeAnchorPosition = pos;
|
Ball.RelativeAnchorPosition = pos;
|
||||||
|
|
||||||
drawSizeLayout.Validate();
|
relativeAnchorPositionLayout.Validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
{
|
{
|
||||||
@ -14,6 +16,22 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
|
||||||
|
{
|
||||||
|
ScoreRank rank = base.RankFromScore(accuracy, results);
|
||||||
|
|
||||||
|
switch (rank)
|
||||||
|
{
|
||||||
|
case ScoreRank.S:
|
||||||
|
case ScoreRank.X:
|
||||||
|
if (results.GetValueOrDefault(HitResult.Miss) > 0)
|
||||||
|
rank = ScoreRank.A;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rank;
|
||||||
|
}
|
||||||
|
|
||||||
protected override HitEvent CreateHitEvent(JudgementResult result)
|
protected override HitEvent CreateHitEvent(JudgementResult result)
|
||||||
=> base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
|
=> base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
|
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
|
||||||
{
|
{
|
||||||
this.RotateTo(-45);
|
this.RotateTo(-45);
|
||||||
this.ScaleTo(1.8f);
|
this.ScaleTo(1.6f);
|
||||||
this.ScaleTo(1.2f, 100, Easing.In);
|
this.ScaleTo(1.2f, 100, Easing.In);
|
||||||
|
|
||||||
this.MoveTo(Vector2.Zero);
|
this.FadeOutFromOne(400);
|
||||||
this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint);
|
|
||||||
}
|
}
|
||||||
else if (Result.IsMiss())
|
else if (Result.IsMiss())
|
||||||
{
|
{
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
|
||||||
this.ScaleTo(1.6f);
|
this.ScaleTo(1.6f);
|
||||||
this.ScaleTo(1, 100, Easing.In);
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
@ -84,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
|
||||||
JudgementText
|
JudgementText
|
||||||
.FadeInFromZero(300, Easing.OutQuint)
|
.FadeInFromZero(300, Easing.OutQuint)
|
||||||
.ScaleTo(Vector2.One)
|
.ScaleTo(Vector2.One)
|
||||||
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.FadeOutFromOne(800);
|
|
||||||
|
|
||||||
ringExplosion?.PlayAnimation();
|
ringExplosion?.PlayAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,47 +3,39 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
public partial class DefaultApproachCircle : SkinnableSprite
|
public partial class DefaultApproachCircle : Sprite
|
||||||
{
|
{
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
public DefaultApproachCircle()
|
private IBindable<Color4> accentColour = null!;
|
||||||
: base("Gameplay/osu/approachcircle")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||||
|
|
||||||
|
// account for the sprite being used for the default approach circle being taken from stable,
|
||||||
|
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||||
|
Scale = new Vector2(128 / 118f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
accentColour.BindValueChanged(colour => Colour = colour.NewValue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Drawable CreateDefault(ISkinComponentLookup lookup)
|
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||||
{
|
accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||||
var drawable = base.CreateDefault(lookup);
|
|
||||||
|
|
||||||
// Although this is a non-legacy component, osu-resources currently stores approach circle as a legacy-like texture.
|
|
||||||
// See LegacyApproachCircle for documentation as to why this is required.
|
|
||||||
drawable.Scale = new Vector2(128 / 118f);
|
|
||||||
|
|
||||||
return drawable;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
TriangleScale = 1.2f;
|
TriangleScale = 1.2f;
|
||||||
HideAlphaDiscrepancies = false;
|
HideAlphaDiscrepancies = false;
|
||||||
|
ClampAxes = Axes.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -12,40 +13,31 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
// todo: this should probably not be a SkinnableSprite, as this is always created for legacy skins and is recreated on skin change.
|
public partial class LegacyApproachCircle : Sprite
|
||||||
public partial class LegacyApproachCircle : SkinnableSprite
|
|
||||||
{
|
{
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
public LegacyApproachCircle()
|
private IBindable<Color4> accentColour = null!;
|
||||||
: base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
var texture = skin.GetTexture(@"approachcircle");
|
||||||
|
Debug.Assert(texture != null);
|
||||||
|
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||||
|
|
||||||
|
// account for the sprite being used for the default approach circle being taken from stable,
|
||||||
|
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||||
|
Scale = new Vector2(128 / 118f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateDefault(ISkinComponentLookup lookup)
|
|
||||||
{
|
|
||||||
var drawable = base.CreateDefault(lookup);
|
|
||||||
|
|
||||||
// account for the sprite being used for the default approach circle being taken from stable,
|
|
||||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
|
||||||
drawable.Scale = new Vector2(128 / 118f);
|
|
||||||
|
|
||||||
return drawable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,19 +34,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skinSource)
|
private void load(DrawableHitObject drawableObject, ISkinSource skinSource)
|
||||||
{
|
{
|
||||||
|
const string lookup_name = @"reversearrow";
|
||||||
|
|
||||||
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
|
var skin = skinSource.FindProvider(s => s.GetTexture(lookup_name) != null);
|
||||||
|
|
||||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
|
||||||
|
|
||||||
InternalChild = arrow = new Sprite
|
InternalChild = arrow = new Sprite
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = skin?.GetTexture(lookupName)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2),
|
Texture = skin?.GetTexture(lookup_name)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2),
|
||||||
};
|
};
|
||||||
|
|
||||||
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||||
|
@ -163,7 +163,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.ApproachCircle:
|
case OsuSkinComponents.ApproachCircle:
|
||||||
return new LegacyApproachCircle();
|
if (GetTexture(@"approachcircle") != null)
|
||||||
|
return new LegacyApproachCircle();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedSkinComponentException(lookup);
|
throw new UnsupportedSkinComponentException(lookup);
|
||||||
|
@ -30,8 +30,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(200),
|
Size = new Vector2(180f, 200f),
|
||||||
Child = new InputDrum()
|
Child = new InputDrum
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
{
|
{
|
||||||
@ -37,11 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Beatmap.Value.Track.Start();
|
Beatmap.Value.Track.Start();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield
|
AddStep("Load playfield", () => SetContents(_ => new Container
|
||||||
{
|
{
|
||||||
Height = 0.2f,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(2f, 1f),
|
||||||
|
Scale = new Vector2(0.5f),
|
||||||
|
Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +59,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestHeightChanges()
|
public void TestHeightChanges()
|
||||||
{
|
{
|
||||||
AddRepeatStep("change height", () => this.ChildrenOfType<TaikoPlayfield>().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50);
|
int value = 0;
|
||||||
|
|
||||||
|
AddRepeatStep("change height", () =>
|
||||||
|
{
|
||||||
|
value = (value + 1) % 5;
|
||||||
|
|
||||||
|
this.ChildrenOfType<TaikoPlayfieldAdjustmentContainer>().ForEach(p =>
|
||||||
|
{
|
||||||
|
var parent = (Container)p.Parent.AsNonNull();
|
||||||
|
parent.Scale = new Vector2(0.5f + 0.1f * value);
|
||||||
|
parent.Width = 1f / parent.Scale.X;
|
||||||
|
parent.Height = 0.5f / parent.Scale.Y;
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -16,9 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
||||||
|
|
||||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
|
||||||
playfield.ClassicHitTargetPosition.Value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Scoring
|
namespace osu.Game.Rulesets.Taiko.Scoring
|
||||||
{
|
{
|
||||||
@ -33,6 +35,22 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
* strongScaleValue(result);
|
* strongScaleValue(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
|
||||||
|
{
|
||||||
|
ScoreRank rank = base.RankFromScore(accuracy, results);
|
||||||
|
|
||||||
|
switch (rank)
|
||||||
|
{
|
||||||
|
case ScoreRank.S:
|
||||||
|
case ScoreRank.X:
|
||||||
|
if (results.GetValueOrDefault(HitResult.Miss) > 0)
|
||||||
|
rank = ScoreRank.A;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rank;
|
||||||
|
}
|
||||||
|
|
||||||
public override int GetBaseScoreForResult(HitResult result)
|
public override int GetBaseScoreForResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
|
|
||||||
public ArgonInputDrum()
|
public ArgonInputDrum()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
{
|
{
|
||||||
public DefaultInputDrum()
|
public DefaultInputDrum()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -18,22 +18,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal partial class LegacyInputDrum : Container
|
internal partial class LegacyInputDrum : Container
|
||||||
{
|
{
|
||||||
private Container content = null!;
|
|
||||||
private LegacyHalfDrum left = null!;
|
private LegacyHalfDrum left = null!;
|
||||||
private LegacyHalfDrum right = null!;
|
private LegacyHalfDrum right = null!;
|
||||||
|
|
||||||
public LegacyInputDrum()
|
public LegacyInputDrum()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Both;
|
||||||
AutoSizeAxes = Axes.X;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
Child = content = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
Size = new Vector2(180, 200),
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Sprite
|
new Sprite
|
||||||
@ -66,33 +64,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
const float ratio = 1.6f;
|
const float ratio = 1.6f;
|
||||||
|
|
||||||
// because the right half is flipped, we need to position using width - position to get the true "topleft" origin position
|
// because the right half is flipped, we need to position using width - position to get the true "topleft" origin position
|
||||||
float negativeScaleAdjust = content.Width / ratio;
|
const float negative_scale_adjust = TaikoPlayfield.INPUT_DRUM_WIDTH / ratio;
|
||||||
|
|
||||||
if (skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
|
if (skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
|
||||||
{
|
{
|
||||||
left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio;
|
left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio;
|
||||||
right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio;
|
right.Centre.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio;
|
||||||
left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio;
|
left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio;
|
||||||
right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio;
|
right.Rim.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio;
|
left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio;
|
||||||
right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio;
|
right.Centre.Position = new Vector2(negative_scale_adjust - 54, taiko_bar_y + 31) * ratio;
|
||||||
left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio;
|
left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio;
|
||||||
right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio;
|
right.Rim.Position = new Vector2(negative_scale_adjust - 53, taiko_bar_y + 23) * ratio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
// Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements.
|
|
||||||
// This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement.
|
|
||||||
content.Scale = new Vector2(DrawHeight / content.Size.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A half-drum. Contains one centre and one rim hit.
|
/// A half-drum. Contains one centre and one rim hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -153,16 +142,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
const float alpha_amount = 1;
|
|
||||||
|
|
||||||
const float down_time = 80;
|
const float down_time = 80;
|
||||||
const float up_time = 50;
|
const float up_time = 50;
|
||||||
|
|
||||||
target.Animate(
|
target
|
||||||
t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time)
|
.FadeTo(1, down_time * (1 - target.Alpha), Easing.Out)
|
||||||
).Then(
|
.Delay(100).FadeOut(up_time);
|
||||||
t => t.FadeOut(up_time)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -17,30 +17,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChild = new Container
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Sprite
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Sprite
|
Texture = skin.GetTexture("approachcircle"),
|
||||||
{
|
Scale = new Vector2(0.83f),
|
||||||
Texture = skin.GetTexture("approachcircle"),
|
Alpha = 0.47f, // eyeballed to match stable
|
||||||
Scale = new Vector2(0.83f),
|
Anchor = Anchor.Centre,
|
||||||
Alpha = 0.47f, // eyeballed to match stable
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
},
|
||||||
Origin = Anchor.Centre,
|
new Sprite
|
||||||
},
|
{
|
||||||
new Sprite
|
Texture = skin.GetTexture("taikobigcircle"),
|
||||||
{
|
Scale = new Vector2(0.8f),
|
||||||
Texture = skin.GetTexture("taikobigcircle"),
|
Alpha = 0.22f, // eyeballed to match stable
|
||||||
Scale = new Vector2(0.8f),
|
Anchor = Anchor.Centre,
|
||||||
Alpha = 0.22f, // eyeballed to match stable
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
},
|
||||||
Origin = Anchor.Centre,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal partial class InputDrum : Container
|
internal partial class InputDrum : Container
|
||||||
{
|
{
|
||||||
public InputDrum()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X;
|
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -27,8 +21,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum())
|
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Both,
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
@ -31,10 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float BASE_HEIGHT = 200;
|
public const float BASE_HEIGHT = 200;
|
||||||
|
|
||||||
/// <summary>
|
public const float INPUT_DRUM_WIDTH = 180f;
|
||||||
/// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position.
|
|
||||||
/// </summary>
|
|
||||||
public Bindable<bool> ClassicHitTargetPosition = new BindableBool();
|
|
||||||
|
|
||||||
public Container UnderlayElements { get; private set; } = null!;
|
public Container UnderlayElements { get; private set; } = null!;
|
||||||
|
|
||||||
@ -49,7 +45,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
private ProxyContainer topLevelHitContainer = null!;
|
private ProxyContainer topLevelHitContainer = null!;
|
||||||
private InputDrum inputDrum = null!;
|
private InputDrum inputDrum = null!;
|
||||||
private Container rightArea = null!;
|
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="Playfield.AddNested"/> is purposefully not called on this to prevent i.e. being able to interact
|
/// <see cref="Playfield.AddNested"/> is purposefully not called on this to prevent i.e. being able to interact
|
||||||
@ -61,13 +56,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
const float hit_target_width = BASE_HEIGHT;
|
const float hit_target_width = BASE_HEIGHT;
|
||||||
|
const float hit_target_offset = -24f;
|
||||||
|
|
||||||
inputDrum = new InputDrum
|
inputDrum = new InputDrum
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = INPUT_DRUM_WIDTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
@ -76,8 +72,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
Name = "Left overlay",
|
Name = "Left overlay",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Y,
|
||||||
FillMode = FillMode.Fit,
|
Width = INPUT_DRUM_WIDTH,
|
||||||
BorderColour = colours.Gray0,
|
BorderColour = colours.Gray0,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
@ -93,10 +89,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Y = 0.2f
|
Y = 0.2f
|
||||||
},
|
},
|
||||||
rightArea = new Container
|
new Container
|
||||||
{
|
{
|
||||||
Name = "Right area",
|
Name = "Right area",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Left = INPUT_DRUM_WIDTH },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
@ -104,6 +101,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
Name = "Elements behind hit objects",
|
Name = "Elements behind hit objects",
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = hit_target_width,
|
Width = hit_target_width,
|
||||||
|
X = hit_target_offset,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
|
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
|
||||||
@ -124,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
Name = "Bar line content",
|
Name = "Bar line content",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = hit_target_width / 2 },
|
Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
UnderlayElements = new Container
|
UnderlayElements = new Container
|
||||||
@ -138,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
Name = "Masked hit objects content",
|
Name = "Masked hit objects content",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = hit_target_width / 2 },
|
Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset },
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Child = HitObjectContainer,
|
Child = HitObjectContainer,
|
||||||
},
|
},
|
||||||
@ -146,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
Name = "Overlay content",
|
Name = "Overlay content",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = hit_target_width / 2 },
|
Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
drumRollHitContainer = new DrumRollHitContainer(),
|
drumRollHitContainer = new DrumRollHitContainer(),
|
||||||
@ -220,14 +218,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
// todo: input drum width should be constant.
|
|
||||||
rightArea.Padding = new MarginPadding { Left = inputDrum.DrawWidth };
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Pooling support
|
#region Pooling support
|
||||||
|
|
||||||
public override void Add(HitObject h)
|
public override void Add(HitObject h)
|
||||||
|
@ -10,7 +10,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
@ -23,6 +22,7 @@ using osu.Game.Rulesets.Mania.Mods;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
@ -59,14 +59,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||||
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||||
|
|
||||||
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
|
Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
|
||||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||||
|
|
||||||
Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
|
Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
|
||||||
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
|
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
|
||||||
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
|
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
|
||||||
|
|
||||||
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
|
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
|
||||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||||
|
|
||||||
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||||
@ -252,7 +252,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void AccuracyAndRankOfStableScorePreserved()
|
public void AccuracyOfStableScoreRecomputed()
|
||||||
{
|
{
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
|
|
||||||
@ -261,15 +261,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
// and we want to emulate a stable score here
|
// and we want to emulate a stable score here
|
||||||
using (var sw = new SerializationWriter(memoryStream, true))
|
using (var sw = new SerializationWriter(memoryStream, true))
|
||||||
{
|
{
|
||||||
sw.Write((byte)0); // ruleset id (osu!)
|
sw.Write((byte)3); // ruleset id (mania).
|
||||||
|
// mania is used intentionally as it is the only ruleset wherein default accuracy calculation is changed in lazer
|
||||||
sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
|
sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
|
||||||
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
|
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
|
||||||
sw.Write("username"); // irrelevant to this test
|
sw.Write("username"); // irrelevant to this test
|
||||||
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
|
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
|
||||||
sw.Write((ushort)198); // count300
|
sw.Write((ushort)1); // count300
|
||||||
sw.Write((ushort)1); // count100
|
sw.Write((ushort)0); // count100
|
||||||
sw.Write((ushort)0); // count50
|
sw.Write((ushort)0); // count50
|
||||||
sw.Write((ushort)0); // countGeki
|
sw.Write((ushort)198); // countGeki (perfects / "rainbow 300s" in mania)
|
||||||
sw.Write((ushort)0); // countKatu
|
sw.Write((ushort)0); // countKatu
|
||||||
sw.Write((ushort)1); // countMiss
|
sw.Write((ushort)1); // countMiss
|
||||||
sw.Write(12345678); // total score, irrelevant to this test
|
sw.Write(12345678); // total score, irrelevant to this test
|
||||||
@ -287,13 +288,54 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 100) / (200 * 300)));
|
Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 305 + 300) / (200 * 305)));
|
||||||
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
|
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void AccuracyAndRankOfLazerScorePreserved()
|
public void RankOfStableScoreUsesLazerDefinitions()
|
||||||
|
{
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
|
||||||
|
// local partial implementation of legacy score encoder
|
||||||
|
// this is done half for readability, half because `LegacyScoreEncoder` forces `LATEST_VERSION`
|
||||||
|
// and we want to emulate a stable score here
|
||||||
|
using (var sw = new SerializationWriter(memoryStream, true))
|
||||||
|
{
|
||||||
|
sw.Write((byte)0); // ruleset id (osu!)
|
||||||
|
sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
|
||||||
|
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
|
||||||
|
sw.Write("username"); // irrelevant to this test
|
||||||
|
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
|
||||||
|
sw.Write((ushort)195); // count300
|
||||||
|
sw.Write((ushort)1); // count100
|
||||||
|
sw.Write((ushort)4); // count50
|
||||||
|
sw.Write((ushort)0); // countGeki
|
||||||
|
sw.Write((ushort)0); // countKatu
|
||||||
|
sw.Write((ushort)0); // countMiss
|
||||||
|
sw.Write(12345678); // total score, irrelevant to this test
|
||||||
|
sw.Write((ushort)1000); // max combo, irrelevant to this test
|
||||||
|
sw.Write(false); // full combo, irrelevant to this test
|
||||||
|
sw.Write((int)LegacyMods.Hidden); // mods
|
||||||
|
sw.Write(string.Empty); // hp graph, irrelevant
|
||||||
|
sw.Write(DateTime.Now); // date, irrelevant
|
||||||
|
sw.Write(Array.Empty<byte>()); // replay data, irrelevant
|
||||||
|
sw.Write((long)1234); // legacy online ID, irrelevant
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var decoded = new TestLegacyScoreDecoder().Parse(memoryStream);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
// In stable this would be an A because there are over 1% 50s. But that's not a thing in lazer.
|
||||||
|
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AccuracyRankAndTotalScoreOfLazerScorePreserved()
|
||||||
{
|
{
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
@ -321,8 +363,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(284_537));
|
||||||
|
Assert.That(decodedAfterEncode.ScoreInfo.LegacyTotalScore, Is.Null);
|
||||||
Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30)));
|
Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30)));
|
||||||
Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
|
Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,6 +459,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
Difficulty = new BeatmapDifficulty(),
|
Difficulty = new BeatmapDifficulty(),
|
||||||
BeatmapVersion = beatmapVersion,
|
BeatmapVersion = beatmapVersion,
|
||||||
|
},
|
||||||
|
// needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die
|
||||||
|
// when trying to recompute total score.
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
@ -210,31 +211,6 @@ namespace osu.Game.Tests.Database
|
|||||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001));
|
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNonLegacyScoreNotSubjectToUpgrades()
|
|
||||||
{
|
|
||||||
ScoreInfo scoreInfo = null!;
|
|
||||||
TestBackgroundDataStoreProcessor processor = null!;
|
|
||||||
|
|
||||||
AddStep("Add score which requires upgrade (and has beatmap)", () =>
|
|
||||||
{
|
|
||||||
Realm.Write(r =>
|
|
||||||
{
|
|
||||||
r.Add(scoreInfo = new ScoreInfo(ruleset: r.All<RulesetInfo>().First(), beatmap: r.All<BeatmapInfo>().First())
|
|
||||||
{
|
|
||||||
TotalScoreVersion = 30000005,
|
|
||||||
LegacyTotalScore = 123456,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
|
||||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
|
||||||
|
|
||||||
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
|
||||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005));
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor
|
public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor
|
||||||
{
|
{
|
||||||
protected override int TimeToSleepDuringGameplay => 10;
|
protected override int TimeToSleepDuringGameplay => 10;
|
||||||
|
@ -56,24 +56,6 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
"Gameplay/osu/followpoint", 1
|
"Gameplay/osu/followpoint", 1
|
||||||
},
|
},
|
||||||
new object[]
|
new object[]
|
||||||
{
|
|
||||||
new[] { "followpoint@2x", "followpoint" },
|
|
||||||
"Gameplay/osu/followpoint",
|
|
||||||
"followpoint@2x", 2
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
new[] { "followpoint@2x" },
|
|
||||||
"Gameplay/osu/followpoint",
|
|
||||||
"followpoint@2x", 2
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
new[] { "followpoint" },
|
|
||||||
"Gameplay/osu/followpoint",
|
|
||||||
"followpoint", 1
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
{
|
||||||
// Looking up a filename with extension specified should work.
|
// Looking up a filename with extension specified should work.
|
||||||
new[] { "followpoint.png" },
|
new[] { "followpoint.png" },
|
||||||
|
@ -29,7 +29,8 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
ColourDark = Color4.Gray,
|
ColourDark = Color4.Gray,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(0.9f)
|
Size = new Vector2(0.9f),
|
||||||
|
ClampAxes = Axes.None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -40,7 +41,10 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
|
|
||||||
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
|
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
|
||||||
AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s));
|
AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s));
|
||||||
AddToggleStep("Masking", m => triangles.Masking = m);
|
AddStep("ClampAxes X", () => triangles.ClampAxes = Axes.X);
|
||||||
|
AddStep("ClampAxes Y", () => triangles.ClampAxes = Axes.Y);
|
||||||
|
AddStep("ClampAxes Both", () => triangles.ClampAxes = Axes.Both);
|
||||||
|
AddStep("ClampAxes None", () => triangles.ClampAxes = Axes.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,8 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ClampAxes = Axes.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -128,7 +129,10 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
|
AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
|
||||||
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
|
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
|
||||||
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
|
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
|
||||||
AddToggleStep("Masking", m => maskedTriangles.Masking = m);
|
AddStep("ClampAxes X", () => maskedTriangles.ClampAxes = Axes.X);
|
||||||
|
AddStep("ClampAxes Y", () => maskedTriangles.ClampAxes = Axes.Y);
|
||||||
|
AddStep("ClampAxes Both", () => maskedTriangles.ClampAxes = Axes.Both);
|
||||||
|
AddStep("ClampAxes None", () => maskedTriangles.ClampAxes = Axes.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,6 +210,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
switchPresets(-1);
|
switchPresets(-1);
|
||||||
assertPreset(BeatDivisorType.Custom, 15);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
assertBeatSnap(15);
|
assertBeatSnap(15);
|
||||||
|
|
||||||
|
setDivisorViaInput(24);
|
||||||
|
assertPreset(BeatDivisorType.Custom, 24);
|
||||||
|
switchPresets(1);
|
||||||
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
switchPresets(-2);
|
||||||
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
||||||
|
@ -129,10 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
checkRate(1);
|
checkRate(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const int max_frames_catchup = 50;
|
|
||||||
|
|
||||||
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
||||||
mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup }
|
mainContainer.Child = new FrameStabilityContainer(gameplayStartTime)
|
||||||
.WithChild(consumer = new ClockConsumingChild()));
|
.WithChild(consumer = new ClockConsumingChild()));
|
||||||
|
|
||||||
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
||||||
|
@ -93,15 +93,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
double currentTime = masterClock.CurrentTime;
|
double currentTime = masterClock.CurrentTime;
|
||||||
|
|
||||||
bool goingForward = currentTime >= (masterClock.LastStopTime ?? lastStopTime);
|
bool goingForward = currentTime >= lastStopTime;
|
||||||
|
|
||||||
alwaysGoingForward &= goingForward;
|
alwaysGoingForward &= goingForward;
|
||||||
|
|
||||||
if (!goingForward)
|
if (!goingForward)
|
||||||
Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})");
|
Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})");
|
||||||
|
|
||||||
if (masterClock.LastStopTime != null)
|
|
||||||
lastStopTime = masterClock.LastStopTime.Value;
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
resumeAndConfirm();
|
resumeAndConfirm();
|
||||||
|
|
||||||
AddAssert("Resumed without seeking forward", () => Player.LastResumeTime, () => Is.LessThanOrEqualTo(Player.LastPauseTime));
|
AddAssert("continued playing forward", () => Player.LastResumeTime, () => Is.GreaterThanOrEqualTo(Player.LastPauseTime));
|
||||||
|
|
||||||
AddUntilStep("player playing", () => Player.LocalUserPlaying.Value);
|
AddUntilStep("player playing", () => Player.LocalUserPlaying.Value);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.Taiko;
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
@ -383,6 +384,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AllowImportCompletion = new SemaphoreSlim(1);
|
AllowImportCompletion = new SemaphoreSlim(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
|
||||||
|
{
|
||||||
|
ShouldValidatePlaybackRate = false,
|
||||||
|
};
|
||||||
|
|
||||||
protected override async Task ImportScore(Score score)
|
protected override async Task ImportScore(Score score)
|
||||||
{
|
{
|
||||||
ScoreImportStarted = true;
|
ScoreImportStarted = true;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
Child = frameStabilityContainer = new FrameStabilityContainer
|
Child = frameStabilityContainer = new FrameStabilityContainer
|
||||||
{
|
{
|
||||||
MaxCatchUpFrames = 1
|
Child = new FakeLoad()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -56,6 +57,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Dependencies.CacheAs<IFrameStableClock>(frameStabilityContainer);
|
Dependencies.CacheAs<IFrameStableClock>(frameStabilityContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class FakeLoad : Drawable
|
||||||
|
{
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetupSteps()
|
public void SetupSteps()
|
||||||
{
|
{
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
});
|
});
|
||||||
AddStep("create IPC sender channels", () =>
|
AddStep("create IPC sender channels", () =>
|
||||||
{
|
{
|
||||||
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true });
|
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPort = OsuGame.IPC_PORT });
|
||||||
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
|
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
|
||||||
archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
|
archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -45,6 +46,16 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
addCircleStep(createScore(1, new OsuRuleset()));
|
addCircleStep(createScore(1, new OsuRuleset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuRankHidden()
|
||||||
|
{
|
||||||
|
addCircleStep(createScore(0, new OsuRuleset(), 20, true));
|
||||||
|
addCircleStep(createScore(0.8, new OsuRuleset(), 5, true));
|
||||||
|
addCircleStep(createScore(0.95, new OsuRuleset(), 0, true));
|
||||||
|
addCircleStep(createScore(0.97, new OsuRuleset(), 1, true));
|
||||||
|
addCircleStep(createScore(1, new OsuRuleset(), 0, true));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCatchRank()
|
public void TestCatchRank()
|
||||||
{
|
{
|
||||||
@ -65,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
addCircleStep(createScore(1, new CatchRuleset()));
|
addCircleStep(createScore(1, new CatchRuleset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () =>
|
private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy}, {score.Statistics.GetValueOrDefault(HitResult.Miss)} miss)", () =>
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -92,10 +103,22 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
private ScoreInfo createScore(double accuracy, Ruleset ruleset)
|
private ScoreInfo createScore(double accuracy, Ruleset ruleset, int missCount = 0, bool hidden = false)
|
||||||
{
|
{
|
||||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
var statistics = new Dictionary<HitResult, int>
|
||||||
|
{
|
||||||
|
{ HitResult.Miss, missCount },
|
||||||
|
{ HitResult.Meh, 50 },
|
||||||
|
{ HitResult.Good, 100 },
|
||||||
|
{ HitResult.Great, 300 },
|
||||||
|
};
|
||||||
|
|
||||||
|
var mods = hidden
|
||||||
|
? new[] { new OsuModHidden() }
|
||||||
|
: new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() };
|
||||||
|
|
||||||
return new ScoreInfo
|
return new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -105,19 +128,13 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
},
|
},
|
||||||
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||||
Ruleset = ruleset.RulesetInfo,
|
Ruleset = ruleset.RulesetInfo,
|
||||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
Mods = mods,
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
Accuracy = accuracy,
|
Accuracy = accuracy,
|
||||||
MaxCombo = 999,
|
MaxCombo = 999,
|
||||||
Rank = scoreProcessor.RankFromAccuracy(accuracy),
|
Rank = scoreProcessor.RankFromScore(accuracy, statistics),
|
||||||
Date = DateTimeOffset.Now,
|
Date = DateTimeOffset.Now,
|
||||||
Statistics =
|
Statistics = statistics,
|
||||||
{
|
|
||||||
{ HitResult.Miss, 1 },
|
|
||||||
{ HitResult.Meh, 50 },
|
|
||||||
{ HitResult.Good, 100 },
|
|
||||||
{ HitResult.Great, 300 },
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -71,15 +72,16 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
|
|
||||||
private int onlineScoreID = 1;
|
private int onlineScoreID = 1;
|
||||||
|
|
||||||
[TestCase(1, ScoreRank.X)]
|
[TestCase(1, ScoreRank.X, 0)]
|
||||||
[TestCase(0.9999, ScoreRank.S)]
|
[TestCase(0.9999, ScoreRank.S, 0)]
|
||||||
[TestCase(0.975, ScoreRank.S)]
|
[TestCase(0.975, ScoreRank.S, 0)]
|
||||||
[TestCase(0.925, ScoreRank.A)]
|
[TestCase(0.975, ScoreRank.A, 1)]
|
||||||
[TestCase(0.85, ScoreRank.B)]
|
[TestCase(0.925, ScoreRank.A, 5)]
|
||||||
[TestCase(0.75, ScoreRank.C)]
|
[TestCase(0.85, ScoreRank.B, 9)]
|
||||||
[TestCase(0.5, ScoreRank.D)]
|
[TestCase(0.75, ScoreRank.C, 11)]
|
||||||
[TestCase(0.2, ScoreRank.D)]
|
[TestCase(0.5, ScoreRank.D, 21)]
|
||||||
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
|
[TestCase(0.2, ScoreRank.D, 51)]
|
||||||
|
public void TestResultsWithPlayer(double accuracy, ScoreRank rank, int missCount)
|
||||||
{
|
{
|
||||||
TestResultsScreen screen = null;
|
TestResultsScreen screen = null;
|
||||||
|
|
||||||
@ -91,6 +93,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
|
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
|
||||||
score.Accuracy = accuracy;
|
score.Accuracy = accuracy;
|
||||||
score.Rank = rank;
|
score.Rank = rank;
|
||||||
|
score.Statistics[HitResult.Miss] = missCount;
|
||||||
|
|
||||||
return screen = createResultsScreen(score);
|
return screen = createResultsScreen(score);
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
<PackageReference Include="Moq" Version="4.18.4" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development"))
|
||||||
{
|
{
|
||||||
host.Run(new TournamentTestBrowser());
|
host.Run(new TournamentTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -27,11 +27,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
private readonly bool applyOffsets;
|
private readonly bool applyOffsets;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The total frequency adjustment from pause transforms. Should eventually be handled in a better way.
|
|
||||||
/// </summary>
|
|
||||||
public readonly BindableDouble ExternalPauseFrequencyAdjust = new BindableDouble(1);
|
|
||||||
|
|
||||||
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
||||||
private readonly OffsetCorrectionClock? platformOffsetClock;
|
private readonly OffsetCorrectionClock? platformOffsetClock;
|
||||||
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
|
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
|
||||||
@ -69,13 +64,13 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
// Audio timings in general with newer BASS versions don't match stable.
|
// Audio timings in general with newer BASS versions don't match stable.
|
||||||
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
||||||
platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
||||||
|
|
||||||
// User global offset (set in settings) should also be applied.
|
// User global offset (set in settings) should also be applied.
|
||||||
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock, ExternalPauseFrequencyAdjust);
|
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
|
||||||
|
|
||||||
// User per-beatmap offset will be applied to this final clock.
|
// User per-beatmap offset will be applied to this final clock.
|
||||||
finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock, ExternalPauseFrequencyAdjust);
|
finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -142,6 +142,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
||||||
SetDefault(OsuSetting.KeyOverlay, false);
|
SetDefault(OsuSetting.KeyOverlay, false);
|
||||||
SetDefault(OsuSetting.ReplaySettingsOverlay, true);
|
SetDefault(OsuSetting.ReplaySettingsOverlay, true);
|
||||||
|
SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true);
|
||||||
SetDefault(OsuSetting.GameplayLeaderboard, true);
|
SetDefault(OsuSetting.GameplayLeaderboard, true);
|
||||||
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
||||||
|
|
||||||
@ -421,6 +422,7 @@ namespace osu.Game.Configuration
|
|||||||
ProfileCoverExpanded,
|
ProfileCoverExpanded,
|
||||||
EditorLimitedDistanceSnap,
|
EditorLimitedDistanceSnap,
|
||||||
ReplaySettingsOverlay,
|
ReplaySettingsOverlay,
|
||||||
|
ReplayPlaybackControlsExpanded,
|
||||||
AutomaticallyDownloadMissingBeatmaps,
|
AutomaticallyDownloadMissingBeatmaps,
|
||||||
EditorShowSpeedChanges,
|
EditorShowSpeedChanges,
|
||||||
TouchDisableGameplayTaps,
|
TouchDisableGameplayTaps,
|
||||||
|
@ -12,7 +12,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -22,7 +21,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs background updating of data stores at startup.
|
/// Performs background updating of data stores at startup.
|
||||||
@ -74,6 +73,7 @@ namespace osu.Game
|
|||||||
processBeatmapsWithMissingObjectCounts();
|
processBeatmapsWithMissingObjectCounts();
|
||||||
processScoresWithMissingStatistics();
|
processScoresWithMissingStatistics();
|
||||||
convertLegacyTotalScoreToStandardised();
|
convertLegacyTotalScoreToStandardised();
|
||||||
|
upgradeScoreRanks();
|
||||||
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
|
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (t.Exception?.InnerException is ObjectDisposedException)
|
if (t.Exception?.InnerException is ObjectDisposedException)
|
||||||
@ -355,7 +355,7 @@ namespace osu.Game
|
|||||||
realmAccess.Write(r =>
|
realmAccess.Write(r =>
|
||||||
{
|
{
|
||||||
ScoreInfo s = r.Find<ScoreInfo>(id)!;
|
ScoreInfo s = r.Find<ScoreInfo>(id)!;
|
||||||
StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager);
|
StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager.GetWorkingBeatmap(s.BeatmapInfo));
|
||||||
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
|
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -376,6 +376,66 @@ namespace osu.Game
|
|||||||
completeNotification(notification, processedCount, scoreIds.Count, failedCount);
|
completeNotification(notification, processedCount, scoreIds.Count, failedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void upgradeScoreRanks()
|
||||||
|
{
|
||||||
|
Logger.Log("Querying for scores that need rank upgrades...");
|
||||||
|
|
||||||
|
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(
|
||||||
|
r.All<ScoreInfo>()
|
||||||
|
.Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION)
|
||||||
|
.AsEnumerable()
|
||||||
|
// must be done after materialisation, as realm doesn't support
|
||||||
|
// filtering on nested property predicates or projection via `.Select()`
|
||||||
|
.Where(s => s.Ruleset.IsLegacyRuleset())
|
||||||
|
.Select(s => s.ID)));
|
||||||
|
|
||||||
|
Logger.Log($"Found {scoreIds.Count} scores which require rank upgrades.");
|
||||||
|
|
||||||
|
if (scoreIds.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var notification = showProgressNotification(scoreIds.Count, "Adjusting ranks of scores", "scores now have more correct ranks");
|
||||||
|
|
||||||
|
int processedCount = 0;
|
||||||
|
int failedCount = 0;
|
||||||
|
|
||||||
|
foreach (var id in scoreIds)
|
||||||
|
{
|
||||||
|
if (notification?.State == ProgressNotificationState.Cancelled)
|
||||||
|
break;
|
||||||
|
|
||||||
|
updateNotificationProgress(notification, processedCount, scoreIds.Count);
|
||||||
|
|
||||||
|
sleepIfRequired();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Can't use async overload because we're not on the update thread.
|
||||||
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
|
realmAccess.Write(r =>
|
||||||
|
{
|
||||||
|
ScoreInfo s = r.Find<ScoreInfo>(id)!;
|
||||||
|
s.Rank = StandardisedScoreMigrationTools.ComputeRank(s);
|
||||||
|
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
|
||||||
|
});
|
||||||
|
|
||||||
|
++processedCount;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Failed to update rank score {id}: {e}");
|
||||||
|
realmAccess.Write(r => r.Find<ScoreInfo>(id)!.BackgroundReprocessingFailed = true);
|
||||||
|
++failedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completeNotification(notification, processedCount, scoreIds.Count, failedCount);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount)
|
private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount)
|
||||||
{
|
{
|
||||||
if (notification == null)
|
if (notification == null)
|
@ -232,42 +232,60 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
|
/// Updates a <see cref="ScoreInfo"/> to standardised scoring.
|
||||||
|
/// This will recompite the score's <see cref="ScoreInfo.Accuracy"/> (always), <see cref="ScoreInfo.Rank"/> (always),
|
||||||
|
/// and <see cref="ScoreInfo.TotalScore"/> (if the score comes from stable).
|
||||||
|
/// The total score from stable - if any applicable - will be stored to <see cref="ScoreInfo.LegacyTotalScore"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score to update.</param>
|
/// <param name="score">The score to update.</param>
|
||||||
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
|
/// <param name="beatmap">The <see cref="WorkingBeatmap"/> applicable for this score.</param>
|
||||||
public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps)
|
public static void UpdateFromLegacy(ScoreInfo score, WorkingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
score.TotalScore = convertFromLegacyTotalScore(score, beatmaps);
|
var ruleset = score.Ruleset.CreateInstance();
|
||||||
score.Accuracy = ComputeAccuracy(score);
|
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||||
|
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||||
|
score.Rank = computeRank(score, scoreProcessor);
|
||||||
|
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
|
/// Updates a <see cref="ScoreInfo"/> to standardised scoring.
|
||||||
|
/// This will recompute the score's <see cref="ScoreInfo.Accuracy"/> (always), <see cref="ScoreInfo.Rank"/> (always),
|
||||||
|
/// and <see cref="ScoreInfo.TotalScore"/> (if the score comes from stable).
|
||||||
|
/// The total score from stable - if any applicable - will be stored to <see cref="ScoreInfo.LegacyTotalScore"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This overload is intended for server-side flows.
|
||||||
|
/// See: https://github.com/ppy/osu-queue-score-statistics/blob/3681e92ac91c6c61922094bdbc7e92e6217dd0fc/osu.Server.Queues.ScoreStatisticsProcessor/Commands/Queue/BatchInserter.cs
|
||||||
|
/// </remarks>
|
||||||
/// <param name="score">The score to update.</param>
|
/// <param name="score">The score to update.</param>
|
||||||
|
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
|
||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||||
public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
public static void UpdateFromLegacy(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||||
{
|
{
|
||||||
score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes);
|
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
score.Accuracy = ComputeAccuracy(score);
|
|
||||||
|
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||||
|
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||||
|
score.Rank = computeRank(score, scoreProcessor);
|
||||||
|
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
|
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score to convert the total score of.</param>
|
/// <param name="score">The score to convert the total score of.</param>
|
||||||
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
|
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
|
||||||
|
/// <param name="beatmap">The <see cref="WorkingBeatmap"/> applicable for this score.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
|
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
WorkingBeatmap beatmap = beatmaps.GetWorkingBeatmap(score.BeatmapInfo);
|
|
||||||
Ruleset ruleset = score.Ruleset.CreateInstance();
|
|
||||||
|
|
||||||
if (ruleset is not ILegacyRuleset legacyRuleset)
|
if (ruleset is not ILegacyRuleset legacyRuleset)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
@ -283,24 +301,24 @@ namespace osu.Game.Database
|
|||||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||||
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
||||||
|
|
||||||
return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
|
return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
|
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score to convert the total score of.</param>
|
/// <param name="score">The score to convert the total score of.</param>
|
||||||
|
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
|
||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
Debug.Assert(score.LegacyTotalScore != null);
|
Debug.Assert(score.LegacyTotalScore != null);
|
||||||
|
|
||||||
Ruleset ruleset = score.Ruleset.CreateInstance();
|
|
||||||
if (ruleset is not ILegacyRuleset legacyRuleset)
|
if (ruleset is not ILegacyRuleset legacyRuleset)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
@ -474,14 +492,9 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
// in the mania case accuracy actually changes between score V1 and score V2 / standardised
|
|
||||||
// (PERFECT weighting changes from 300 to 305),
|
|
||||||
// so for better accuracy recompute accuracy locally based on hit statistics and use that instead,
|
|
||||||
double scoreV2Accuracy = ComputeAccuracy(score);
|
|
||||||
|
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScore = (long)Math.Round((
|
||||||
850000 * comboProportion
|
850000 * comboProportion
|
||||||
+ 150000 * Math.Pow(scoreV2Accuracy, 2 + 2 * scoreV2Accuracy)
|
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion) * modMultiplier);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -584,11 +597,8 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double ComputeAccuracy(ScoreInfo scoreInfo)
|
private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
|
|
||||||
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
|
|
||||||
|
|
||||||
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||||
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||||
@ -597,6 +607,18 @@ namespace osu.Game.Database
|
|||||||
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
|
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
|
||||||
|
|
||||||
|
private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics);
|
||||||
|
|
||||||
|
foreach (var mod in scoreInfo.Mods.OfType<IApplicableToScoreProcessor>())
|
||||||
|
rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
|
||||||
|
|
||||||
|
return rank;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
|
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
|
||||||
using osu.Framework.Lists;
|
using osu.Framework.Lists;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
@ -78,10 +77,10 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If enabled, only the portion of triangles that falls within this <see cref="Drawable"/>'s
|
/// Controls on which <see cref="Axes"/> the portion of triangles that falls within this <see cref="Drawable"/>'s
|
||||||
/// shape is drawn to the screen.
|
/// shape is drawn to the screen. Default is Axes.Both.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Masking { get; set; }
|
public Axes ClampAxes { get; set; } = Axes.Both;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether we should drop-off alpha values of triangles more quickly to improve
|
/// Whether we should drop-off alpha values of triangles more quickly to improve
|
||||||
@ -258,13 +257,12 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
|
|
||||||
private IShader shader;
|
private IShader shader;
|
||||||
private Texture texture;
|
private Texture texture;
|
||||||
private bool masking;
|
private Axes clampAxes;
|
||||||
|
|
||||||
private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
|
private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
|
||||||
private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size;
|
private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size;
|
||||||
|
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
private IVertexBatch<TexturedVertex2D> vertexBatch;
|
|
||||||
|
|
||||||
public TrianglesDrawNode(Triangles source)
|
public TrianglesDrawNode(Triangles source)
|
||||||
: base(source)
|
: base(source)
|
||||||
@ -278,7 +276,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
shader = Source.shader;
|
shader = Source.shader;
|
||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.DrawSize;
|
size = Source.DrawSize;
|
||||||
masking = Source.Masking;
|
clampAxes = Source.ClampAxes;
|
||||||
|
|
||||||
parts.Clear();
|
parts.Clear();
|
||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
@ -290,12 +288,6 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
|
|
||||||
if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount))
|
|
||||||
{
|
|
||||||
vertexBatch?.Dispose();
|
|
||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
borderDataBuffer.Data = borderDataBuffer.Data with
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
{
|
{
|
||||||
@ -314,7 +306,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
|
|
||||||
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
|
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
|
||||||
|
|
||||||
Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
|
Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize);
|
||||||
|
|
||||||
var drawQuad = new Quad(
|
var drawQuad = new Quad(
|
||||||
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
|
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
|
||||||
@ -333,30 +325,35 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
triangleQuad.Height
|
triangleQuad.Height
|
||||||
) / relativeSize;
|
) / relativeSize;
|
||||||
|
|
||||||
renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
|
renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), textureCoords: textureCoords);
|
||||||
}
|
}
|
||||||
|
|
||||||
shader.Unbind();
|
shader.Unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
|
private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size)
|
||||||
{
|
{
|
||||||
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
|
Vector2 clampedTopLeft = topLeft;
|
||||||
float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
|
|
||||||
|
|
||||||
return new Quad(
|
if (clampAxes == Axes.X || clampAxes == Axes.Both)
|
||||||
leftClamped,
|
{
|
||||||
topClamped,
|
clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f);
|
||||||
Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
|
size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X;
|
||||||
Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
|
}
|
||||||
);
|
|
||||||
|
if (clampAxes == Axes.Y || clampAxes == Axes.Both)
|
||||||
|
{
|
||||||
|
clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f);
|
||||||
|
size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
|
||||||
borderDataBuffer?.Dispose();
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
@ -34,10 +33,10 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
protected virtual bool CreateNewTriangles => true;
|
protected virtual bool CreateNewTriangles => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If enabled, only the portion of triangles that falls within this <see cref="Drawable"/>'s
|
/// Controls on which <see cref="Axes"/> the portion of triangles that falls within this <see cref="Drawable"/>'s
|
||||||
/// shape is drawn to the screen.
|
/// shape is drawn to the screen. Default is Axes.Both.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Masking { get; set; }
|
public Axes ClampAxes { get; set; } = Axes.Both;
|
||||||
|
|
||||||
private readonly BindableFloat spawnRatio = new BindableFloat(1f);
|
private readonly BindableFloat spawnRatio = new BindableFloat(1f);
|
||||||
|
|
||||||
@ -194,9 +193,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
private float thickness;
|
private float thickness;
|
||||||
private float texelSize;
|
private float texelSize;
|
||||||
private bool masking;
|
private Axes clampAxes;
|
||||||
|
|
||||||
private IVertexBatch<TexturedVertex2D>? vertexBatch;
|
|
||||||
|
|
||||||
public TrianglesDrawNode(TrianglesV2 source)
|
public TrianglesDrawNode(TrianglesV2 source)
|
||||||
: base(source)
|
: base(source)
|
||||||
@ -211,7 +208,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.DrawSize;
|
size = Source.DrawSize;
|
||||||
thickness = Source.Thickness;
|
thickness = Source.Thickness;
|
||||||
masking = Source.Masking;
|
clampAxes = Source.ClampAxes;
|
||||||
|
|
||||||
Quad triangleQuad = new Quad(
|
Quad triangleQuad = new Quad(
|
||||||
Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix),
|
Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix),
|
||||||
@ -235,12 +232,6 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
if (Source.AimCount == 0 || thickness == 0)
|
if (Source.AimCount == 0 || thickness == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (vertexBatch == null || vertexBatch.Size != Source.AimCount)
|
|
||||||
{
|
|
||||||
vertexBatch?.Dispose();
|
|
||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
borderDataBuffer.Data = borderDataBuffer.Data with
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
{
|
{
|
||||||
@ -257,7 +248,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
|
Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
|
||||||
|
|
||||||
Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
|
Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize);
|
||||||
|
|
||||||
var drawQuad = new Quad(
|
var drawQuad = new Quad(
|
||||||
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
|
Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
|
||||||
@ -273,30 +264,35 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
triangleQuad.Height
|
triangleQuad.Height
|
||||||
) / relativeSize;
|
) / relativeSize;
|
||||||
|
|
||||||
renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
|
renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), textureCoords: textureCoords);
|
||||||
}
|
}
|
||||||
|
|
||||||
shader.Unbind();
|
shader.Unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
|
private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size)
|
||||||
{
|
{
|
||||||
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
|
Vector2 clampedTopLeft = topLeft;
|
||||||
float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
|
|
||||||
|
|
||||||
return new Quad(
|
if (clampAxes == Axes.X || clampAxes == Axes.Both)
|
||||||
leftClamped,
|
{
|
||||||
topClamped,
|
clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f);
|
||||||
Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
|
size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X;
|
||||||
Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
|
}
|
||||||
);
|
|
||||||
|
if (clampAxes == Axes.Y || clampAxes == Axes.Both)
|
||||||
|
{
|
||||||
|
clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f);
|
||||||
|
size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
|
||||||
borderDataBuffer?.Dispose();
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
foreach (var w in wavesContainer.Children)
|
foreach (var w in wavesContainer)
|
||||||
w.Show();
|
w.Show();
|
||||||
|
|
||||||
contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint);
|
contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint);
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
{
|
{
|
||||||
foreach (var w in wavesContainer.Children)
|
foreach (var w in wavesContainer)
|
||||||
w.Hide();
|
w.Hide();
|
||||||
|
|
||||||
contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In);
|
contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -24,6 +22,8 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
namespace osu.Game.Graphics
|
namespace osu.Game.Graphics
|
||||||
{
|
{
|
||||||
@ -37,30 +37,26 @@ namespace osu.Game.Graphics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IBindable<bool> CursorVisibility => cursorVisibility;
|
public IBindable<bool> CursorVisibility => cursorVisibility;
|
||||||
|
|
||||||
private Bindable<ScreenshotFormat> screenshotFormat;
|
[Resolved]
|
||||||
private Bindable<bool> captureMenuCursor;
|
private GameHost host { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private Clipboard clipboard { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Clipboard clipboard { get; set; }
|
private INotificationOverlay notificationOverlay { get; set; } = null!;
|
||||||
|
|
||||||
private Storage storage;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private INotificationOverlay notificationOverlay { get; set; }
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
private Sample shutter;
|
private Storage storage = null!;
|
||||||
|
|
||||||
|
private Sample? shutter;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config, Storage storage, AudioManager audio)
|
private void load(Storage storage, AudioManager audio)
|
||||||
{
|
{
|
||||||
this.storage = storage.GetStorageForDirectory(@"screenshots");
|
this.storage = storage.GetStorageForDirectory(@"screenshots");
|
||||||
|
|
||||||
screenshotFormat = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat);
|
|
||||||
captureMenuCursor = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor);
|
|
||||||
|
|
||||||
shutter = audio.Samples.Get("UI/shutter");
|
shutter = audio.Samples.Get("UI/shutter");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +68,7 @@ namespace osu.Game.Graphics
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.TakeScreenshot:
|
case GlobalAction.TakeScreenshot:
|
||||||
shutter.Play();
|
shutter?.Play();
|
||||||
TakeScreenshotAsync().FireAndForget();
|
TakeScreenshotAsync().FireAndForget();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -90,9 +86,12 @@ namespace osu.Game.Graphics
|
|||||||
{
|
{
|
||||||
Interlocked.Increment(ref screenShotTasks);
|
Interlocked.Increment(ref screenShotTasks);
|
||||||
|
|
||||||
|
ScreenshotFormat screenshotFormat = config.Get<ScreenshotFormat>(OsuSetting.ScreenshotFormat);
|
||||||
|
bool captureMenuCursor = config.Get<bool>(OsuSetting.ScreenshotCaptureMenuCursor);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!captureMenuCursor.Value)
|
if (!captureMenuCursor)
|
||||||
{
|
{
|
||||||
cursorVisibility.Value = false;
|
cursorVisibility.Value = false;
|
||||||
|
|
||||||
@ -101,7 +100,7 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
int framesWaited = 0;
|
int framesWaited = 0;
|
||||||
|
|
||||||
using (var framesWaitedEvent = new ManualResetEventSlim(false))
|
using (ManualResetEventSlim framesWaitedEvent = new ManualResetEventSlim(false))
|
||||||
{
|
{
|
||||||
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
|
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
|
||||||
{
|
{
|
||||||
@ -117,17 +116,41 @@ namespace osu.Game.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
|
using (Image<Rgba32>? image = await host.TakeScreenshotAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
if (config.Get<ScalingMode>(OsuSetting.Scaling) == ScalingMode.Everything)
|
||||||
|
{
|
||||||
|
float posX = config.Get<float>(OsuSetting.ScalingPositionX);
|
||||||
|
float posY = config.Get<float>(OsuSetting.ScalingPositionY);
|
||||||
|
float sizeX = config.Get<float>(OsuSetting.ScalingSizeX);
|
||||||
|
float sizeY = config.Get<float>(OsuSetting.ScalingSizeY);
|
||||||
|
|
||||||
|
image.Mutate(m =>
|
||||||
|
{
|
||||||
|
Rectangle rect = new Rectangle(Point.Empty, m.GetCurrentSize());
|
||||||
|
|
||||||
|
// Reduce size by user scale settings...
|
||||||
|
int sx = (rect.Width - (int)(rect.Width * sizeX)) / 2;
|
||||||
|
int sy = (rect.Height - (int)(rect.Height * sizeY)) / 2;
|
||||||
|
rect.Inflate(-sx, -sy);
|
||||||
|
|
||||||
|
// ...then adjust the region based on their positional offset.
|
||||||
|
rect.X = (int)(rect.X * posX) * 2;
|
||||||
|
rect.Y = (int)(rect.Y * posY) * 2;
|
||||||
|
|
||||||
|
m.Crop(rect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
clipboard.SetImage(image);
|
clipboard.SetImage(image);
|
||||||
|
|
||||||
(string filename, var stream) = getWritableStream();
|
(string? filename, Stream? stream) = getWritableStream(screenshotFormat);
|
||||||
|
|
||||||
if (filename == null) return;
|
if (filename == null) return;
|
||||||
|
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
switch (screenshotFormat.Value)
|
switch (screenshotFormat)
|
||||||
{
|
{
|
||||||
case ScreenshotFormat.Png:
|
case ScreenshotFormat.Png:
|
||||||
await image.SaveAsPngAsync(stream).ConfigureAwait(false);
|
await image.SaveAsPngAsync(stream).ConfigureAwait(false);
|
||||||
@ -140,7 +163,7 @@ namespace osu.Game.Graphics
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
|
throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,12 +187,12 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
private static readonly object filename_reservation_lock = new object();
|
private static readonly object filename_reservation_lock = new object();
|
||||||
|
|
||||||
private (string filename, Stream stream) getWritableStream()
|
private (string? filename, Stream? stream) getWritableStream(ScreenshotFormat format)
|
||||||
{
|
{
|
||||||
lock (filename_reservation_lock)
|
lock (filename_reservation_lock)
|
||||||
{
|
{
|
||||||
var dt = DateTime.Now;
|
DateTime dt = DateTime.Now;
|
||||||
string fileExt = screenshotFormat.ToString().ToLowerInvariant();
|
string fileExt = format.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
|
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
|
||||||
if (!storage.Exists(withoutIndex))
|
if (!storage.Exists(withoutIndex))
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
Current.ValueChanged += index =>
|
Current.ValueChanged += index =>
|
||||||
{
|
{
|
||||||
foreach (var t in TabContainer.Children.OfType<BreadcrumbTabItem>())
|
foreach (var t in TabContainer.OfType<BreadcrumbTabItem>())
|
||||||
{
|
{
|
||||||
int tIndex = TabContainer.IndexOf(t);
|
int tIndex = TabContainer.IndexOf(t);
|
||||||
int tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]);
|
int tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]);
|
||||||
|
@ -150,6 +150,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
TriangleScale = 4,
|
TriangleScale = 4,
|
||||||
ColourDark = OsuColour.Gray(0.88f),
|
ColourDark = OsuColour.Gray(0.88f),
|
||||||
Shear = new Vector2(-0.2f, 0),
|
Shear = new Vector2(-0.2f, 0),
|
||||||
|
ClampAxes = Axes.Y
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
if (Dropdown is IHasAccentColour dropdown)
|
if (Dropdown is IHasAccentColour dropdown)
|
||||||
dropdown.AccentColour = value;
|
dropdown.AccentColour = value;
|
||||||
foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
|
foreach (var i in TabContainer.OfType<IHasAccentColour>())
|
||||||
i.AccentColour = value;
|
i.AccentColour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override TabItem<T> CreateTabItem(T value) => new OsuTabItem(value);
|
protected override TabItem<T> CreateTabItem(T value) => new OsuTabItem(value);
|
||||||
|
|
||||||
protected virtual float StripWidth => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
|
protected virtual float StripWidth => TabContainer.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether entries should be automatically populated if <typeparamref name="T"/> is an <see cref="Enum"/> type.
|
/// Whether entries should be automatically populated if <typeparamref name="T"/> is an <see cref="Enum"/> type.
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -39,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override double GetProportionalDuration(long currentValue, long newValue) =>
|
protected override double GetProportionalDuration(long currentValue, long newValue) =>
|
||||||
currentValue > newValue ? currentValue - newValue : newValue - currentValue;
|
currentValue > newValue ? currentValue - newValue : newValue - currentValue;
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(formatString);
|
protected override LocalisableString FormatCount(long count) => count.ToString(formatString);
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText()
|
protected override OsuSpriteText CreateSpriteText()
|
||||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public void StopAnimation()
|
public void StopAnimation()
|
||||||
{
|
{
|
||||||
animate(current);
|
animate(current);
|
||||||
foreach (var star in stars.Children)
|
foreach (var star in stars)
|
||||||
star.FinishTransforms(true);
|
star.FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.IO
|
namespace osu.Game.IO
|
||||||
{
|
{
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.IO
|
|||||||
if (IgnoreSuffixes.Any(suffix => fi.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
if (IgnoreSuffixes.Any(suffix => fi.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false);
|
allFilesDeleted &= FileUtils.AttemptOperation(() => fi.Delete(), throwOnFailure: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in target.GetDirectories())
|
foreach (DirectoryInfo dir in target.GetDirectories())
|
||||||
@ -92,11 +92,11 @@ namespace osu.Game.IO
|
|||||||
if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
|
allFilesDeleted &= FileUtils.AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
||||||
allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false);
|
allFilesDeleted &= FileUtils.AttemptOperation(target.Delete, throwOnFailure: false);
|
||||||
|
|
||||||
return allFilesDeleted;
|
return allFilesDeleted;
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ namespace osu.Game.IO
|
|||||||
if (IgnoreSuffixes.Any(suffix => fileInfo.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
if (IgnoreSuffixes.Any(suffix => fileInfo.Name.EndsWith(suffix, StringComparison.Ordinal)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AttemptOperation(() =>
|
FileUtils.AttemptOperation(() =>
|
||||||
{
|
{
|
||||||
fileInfo.Refresh();
|
fileInfo.Refresh();
|
||||||
|
|
||||||
@ -139,35 +139,5 @@ namespace osu.Game.IO
|
|||||||
CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false);
|
CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action to perform.</param>
|
|
||||||
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
|
||||||
/// <param name="throwOnFailure">Whether to throw an exception on failure. If <c>false</c>, will silently fail.</param>
|
|
||||||
protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (attempts-- == 0)
|
|
||||||
{
|
|
||||||
if (throwOnFailure)
|
|
||||||
throw;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Sleep(250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,6 +170,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay),
|
new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay),
|
||||||
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
|
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
|
||||||
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
|
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
|
||||||
|
new KeyBinding(InputKey.Comma, GlobalAction.StepReplayBackward),
|
||||||
|
new KeyBinding(InputKey.Period, GlobalAction.StepReplayForward),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.ToggleReplaySettings),
|
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.ToggleReplaySettings),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -411,7 +413,13 @@ namespace osu.Game.Input.Bindings
|
|||||||
IncreaseOffset,
|
IncreaseOffset,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))]
|
||||||
DecreaseOffset
|
DecreaseOffset,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayForward))]
|
||||||
|
StepReplayForward,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
|
||||||
|
StepReplayBackward,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GlobalActionCategory
|
public enum GlobalActionCategory
|
||||||
|
@ -10,9 +10,9 @@ namespace osu.Game.Localisation
|
|||||||
private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOffsetControl";
|
private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOffsetControl";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Beatmap offset"
|
/// "Audio offset (this beatmap)"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString BeatmapOffset => new TranslatableString(getKey(@"beatmap_offset"), @"Beatmap offset");
|
public static LocalisableString AudioOffsetThisBeatmap => new TranslatableString(getKey(@"beatmap_offset"), @"Audio offset (this beatmap)");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Previous play:"
|
/// "Previous play:"
|
||||||
|
@ -324,6 +324,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward");
|
public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Seek replay forward one frame"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StepReplayForward => new TranslatableString(getKey(@"step_replay_forward"), @"Seek replay forward one frame");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Step replay backward one frame"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StepReplayBackward => new TranslatableString(getKey(@"step_replay_backward"), @"Step replay backward one frame");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Toggle chat focus"
|
/// "Toggle chat focus"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -9,6 +9,16 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings";
|
private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Step backward one frame"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StepBackward => new TranslatableString(getKey(@"step_backward_frame"), @"Step backward one frame");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Step forward one frame"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Seek backward {0} seconds"
|
/// "Seek backward {0} seconds"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -287,7 +287,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
double delay = 0;
|
double delay = 0;
|
||||||
|
|
||||||
foreach (var s in scoreFlowContainer.Children)
|
foreach (var s in scoreFlowContainer)
|
||||||
{
|
{
|
||||||
using (s.BeginDelayedSequence(delay))
|
using (s.BeginDelayedSequence(delay))
|
||||||
s.Show();
|
s.Show();
|
||||||
@ -384,7 +384,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
if (scoreFlowContainer == null)
|
if (scoreFlowContainer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var c in scoreFlowContainer.Children)
|
foreach (var c in scoreFlowContainer)
|
||||||
{
|
{
|
||||||
float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y;
|
float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y;
|
||||||
float bottomY = topY + LeaderboardScore.HEIGHT;
|
float bottomY = topY + LeaderboardScore.HEIGHT;
|
||||||
|
@ -80,6 +80,13 @@ namespace osu.Game
|
|||||||
[Cached(typeof(OsuGame))]
|
[Cached(typeof(OsuGame))]
|
||||||
public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
|
public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
// Different port allows runnning release and debug builds alongside each other.
|
||||||
|
public const int IPC_PORT = 44824;
|
||||||
|
#else
|
||||||
|
public const int IPC_PORT = 44823;
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public)
|
flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public)
|
||||||
.Select(c => new ChannelListingItem(c));
|
.Select(c => new ChannelListingItem(c));
|
||||||
|
|
||||||
foreach (var item in flow.Children)
|
foreach (var item in flow)
|
||||||
{
|
{
|
||||||
item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel);
|
item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel);
|
||||||
item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel);
|
item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel);
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV();
|
var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV();
|
||||||
var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f);
|
var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f);
|
||||||
triangles.Colour = ColourInfo.GradientVertical(trianglesColour, trianglesColour.MultiplyAlpha(0f));
|
triangles.Colour = ColourInfo.GradientVertical(trianglesColour, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +95,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Height = header_height,
|
Height = header_height,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
Velocity = 0.7f,
|
Velocity = 0.7f,
|
||||||
|
ClampAxes = Axes.Y
|
||||||
},
|
},
|
||||||
headerText = new OsuTextFlowContainer(t =>
|
headerText = new OsuTextFlowContainer(t =>
|
||||||
{
|
{
|
||||||
|
@ -405,6 +405,8 @@ namespace osu.Game.Overlays
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(150),
|
Colour = OsuColour.Gray(150),
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
foreach (var streamBadge in TabContainer.Children.OfType<OverlayStreamItem<T>>())
|
foreach (var streamBadge in TabContainer.OfType<OverlayStreamItem<T>>())
|
||||||
streamBadge.UserHoveringArea = true;
|
streamBadge.UserHoveringArea = true;
|
||||||
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
foreach (var streamBadge in TabContainer.Children.OfType<OverlayStreamItem<T>>())
|
foreach (var streamBadge in TabContainer.OfType<OverlayStreamItem<T>>())
|
||||||
streamBadge.UserHoveringArea = false;
|
streamBadge.UserHoveringArea = false;
|
||||||
|
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
|
@ -12,12 +12,14 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Audio
|
namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||||
@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new TimeSlider
|
new OffsetSliderBar
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Current = { BindTarget = Current },
|
Current = { BindTarget = Current },
|
||||||
@ -157,6 +159,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
: $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms.";
|
: $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms.";
|
||||||
applySuggestion.Enabled.Value = SuggestedOffset.Value != null;
|
applySuggestion.Enabled.Value = SuggestedOffset.Value != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class OffsetSliderBar : RoundedSliderBar<double>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => BeatmapOffsetControl.GetOffsetExplanatoryText(Current.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Localisation;
|
|||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
|
using osu.Game.Utils;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.General
|
namespace osu.Game.Overlays.Settings.Sections.General
|
||||||
@ -111,7 +112,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
using (var outStream = storage.CreateFileSafely(archive_filename))
|
using (var outStream = storage.CreateFileSafely(archive_filename))
|
||||||
using (var zip = ZipArchive.Create())
|
using (var zip = ZipArchive.Create())
|
||||||
{
|
{
|
||||||
foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) zip.AddEntry(f, logStorage.GetStream(f), true);
|
foreach (string? f in logStorage.GetFiles(string.Empty, "*.log"))
|
||||||
|
FileUtils.AttemptOperation(z => z.AddEntry(f, logStorage.GetStream(f), true), zip);
|
||||||
|
|
||||||
zip.SaveTo(outStream);
|
zip.SaveTo(outStream);
|
||||||
}
|
}
|
||||||
|
@ -45,13 +45,14 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
|
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
|
||||||
{
|
{
|
||||||
this.RotateTo(-45);
|
this.RotateTo(-45);
|
||||||
this.ScaleTo(1.8f);
|
this.ScaleTo(1.6f);
|
||||||
this.ScaleTo(1.2f, 100, Easing.In);
|
this.ScaleTo(1.2f, 100, Easing.In);
|
||||||
|
|
||||||
this.MoveTo(Vector2.Zero);
|
this.FadeOutFromOne(400);
|
||||||
this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint);
|
return;
|
||||||
}
|
}
|
||||||
else if (Result.IsMiss())
|
|
||||||
|
if (Result.IsMiss())
|
||||||
{
|
{
|
||||||
this.ScaleTo(1.6f);
|
this.ScaleTo(1.6f);
|
||||||
this.ScaleTo(1, 100, Easing.In);
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
@ -153,6 +153,9 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
|
|
||||||
protected override bool CheckChildrenLife()
|
protected override bool CheckChildrenLife()
|
||||||
{
|
{
|
||||||
|
if (!IsPresent)
|
||||||
|
return false;
|
||||||
|
|
||||||
bool aliveChanged = base.CheckChildrenLife();
|
bool aliveChanged = base.CheckChildrenLife();
|
||||||
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
||||||
return aliveChanged;
|
return aliveChanged;
|
||||||
|
@ -370,7 +370,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (rank.Value == ScoreRank.F)
|
if (rank.Value == ScoreRank.F)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
rank.Value = RankFromAccuracy(Accuracy.Value);
|
rank.Value = RankFromScore(Accuracy.Value, ScoreResultCounts);
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value);
|
rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value);
|
||||||
}
|
}
|
||||||
@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
|
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual ScoreRank RankFromAccuracy(double accuracy)
|
public virtual ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
|
||||||
{
|
{
|
||||||
if (accuracy == accuracy_cutoff_x)
|
if (accuracy == accuracy_cutoff_x)
|
||||||
return ScoreRank.X;
|
return ScoreRank.X;
|
||||||
|
@ -25,17 +25,15 @@ namespace osu.Game.Rulesets.UI
|
|||||||
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
|
/// The number of CPU milliseconds to spend at most during seek catch-up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxCatchUpFrames { get; set; } = 5;
|
private const double max_catchup_milliseconds = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to enable frame-stable playback.
|
/// Whether to enable frame-stable playback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool FrameStablePlayback { get; set; } = true;
|
internal bool FrameStablePlayback { get; set; } = true;
|
||||||
|
|
||||||
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> isCatchingUp = new Bindable<bool>();
|
private readonly Bindable<bool> isCatchingUp = new Bindable<bool>();
|
||||||
|
|
||||||
private readonly Bindable<bool> waitingOnFrames = new Bindable<bool>();
|
private readonly Bindable<bool> waitingOnFrames = new Bindable<bool>();
|
||||||
@ -61,6 +59,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly FramedClock framedClock;
|
private readonly FramedClock framedClock;
|
||||||
|
|
||||||
|
private readonly Stopwatch stopwatch = new Stopwatch();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current direction of playback to be exposed to frame stable children.
|
/// The current direction of playback to be exposed to frame stable children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public override bool UpdateSubTree()
|
public override bool UpdateSubTree()
|
||||||
{
|
{
|
||||||
int loops = MaxCatchUpFrames;
|
stopwatch.Restart();
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
base.UpdateSubTree();
|
base.UpdateSubTree();
|
||||||
UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat);
|
UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat);
|
||||||
} while (state == PlaybackState.RequiresCatchUp && loops-- > 0);
|
} while (state == PlaybackState.RequiresCatchUp && stopwatch.ElapsedMilliseconds < max_catchup_milliseconds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
// if waiting on frames, run one update loop to determine if frames have arrived.
|
// if waiting on frames, run one update loop to determine if frames have arrived.
|
||||||
state = PlaybackState.Valid;
|
state = PlaybackState.Valid;
|
||||||
}
|
}
|
||||||
else if (IsPaused.Value)
|
else if (IsPaused.Value && !hasReplayAttached)
|
||||||
{
|
{
|
||||||
// time should not advance while paused, nor should anything run.
|
// time should not advance while paused, nor should anything run.
|
||||||
state = PlaybackState.NotValid;
|
state = PlaybackState.NotValid;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -19,6 +20,7 @@ using osu.Game.Replays.Legacy;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
|
|
||||||
namespace osu.Game.Scoring.Legacy
|
namespace osu.Game.Scoring.Legacy
|
||||||
@ -38,7 +40,6 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
};
|
};
|
||||||
|
|
||||||
WorkingBeatmap workingBeatmap;
|
WorkingBeatmap workingBeatmap;
|
||||||
byte[] compressedScoreInfo = null;
|
|
||||||
|
|
||||||
using (SerializationReader sr = new SerializationReader(stream))
|
using (SerializationReader sr = new SerializationReader(stream))
|
||||||
{
|
{
|
||||||
@ -107,6 +108,8 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
else if (version >= 20121008)
|
else if (version >= 20121008)
|
||||||
scoreInfo.LegacyOnlineID = sr.ReadInt32();
|
scoreInfo.LegacyOnlineID = sr.ReadInt32();
|
||||||
|
|
||||||
|
byte[] compressedScoreInfo = null;
|
||||||
|
|
||||||
if (version >= 30000001)
|
if (version >= 30000001)
|
||||||
compressedScoreInfo = sr.ReadByteArray();
|
compressedScoreInfo = sr.ReadByteArray();
|
||||||
|
|
||||||
@ -130,10 +133,12 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score.ScoreInfo.IsLegacyScore || compressedScoreInfo == null)
|
PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap);
|
||||||
PopulateLegacyAccuracyAndRank(score.ScoreInfo);
|
|
||||||
else
|
if (score.ScoreInfo.IsLegacyScore)
|
||||||
populateLazerAccuracyAndRank(score.ScoreInfo);
|
score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore;
|
||||||
|
|
||||||
|
StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap);
|
||||||
|
|
||||||
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
||||||
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
||||||
@ -171,121 +176,65 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Populates the accuracy of a given <see cref="ScoreInfo"/> from its contained statistics.
|
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <param name="score">The score to populate the statistics of.</param>
|
||||||
/// Legacy use only.
|
/// <param name="workingBeatmap">The corresponding <see cref="WorkingBeatmap"/>.</param>
|
||||||
/// </remarks>
|
internal static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap)
|
||||||
/// <param name="score">The <see cref="ScoreInfo"/> to populate.</param>
|
|
||||||
public static void PopulateLegacyAccuracyAndRank(ScoreInfo score)
|
|
||||||
{
|
{
|
||||||
int countMiss = score.GetCountMiss() ?? 0;
|
Debug.Assert(score.BeatmapInfo != null);
|
||||||
int count50 = score.GetCount50() ?? 0;
|
|
||||||
int count100 = score.GetCount100() ?? 0;
|
|
||||||
int count300 = score.GetCount300() ?? 0;
|
|
||||||
int countGeki = score.GetCountGeki() ?? 0;
|
|
||||||
int countKatu = score.GetCountKatu() ?? 0;
|
|
||||||
|
|
||||||
switch (score.Ruleset.OnlineID)
|
if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ruleset = score.Ruleset.Detach();
|
||||||
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
var scoreProcessor = rulesetInstance.CreateScoreProcessor();
|
||||||
|
|
||||||
|
// Populate the maximum statistics.
|
||||||
|
HitResult maxBasicResult = rulesetInstance.GetHitResults()
|
||||||
|
.Select(h => h.result)
|
||||||
|
.Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
|
||||||
|
|
||||||
|
foreach ((HitResult result, int count) in score.Statistics)
|
||||||
{
|
{
|
||||||
case 0:
|
switch (result)
|
||||||
{
|
{
|
||||||
int totalHits = count50 + count100 + count300 + countMiss;
|
case HitResult.LargeTickHit:
|
||||||
score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1;
|
case HitResult.LargeTickMiss:
|
||||||
|
score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
|
||||||
|
break;
|
||||||
|
|
||||||
float ratio300 = (float)count300 / totalHits;
|
case HitResult.SmallTickHit:
|
||||||
float ratio50 = (float)count50 / totalHits;
|
case HitResult.SmallTickMiss:
|
||||||
|
score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
|
||||||
|
break;
|
||||||
|
|
||||||
if (ratio300 == 1)
|
case HitResult.IgnoreHit:
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
|
case HitResult.IgnoreMiss:
|
||||||
else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0)
|
case HitResult.SmallBonus:
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
|
case HitResult.LargeBonus:
|
||||||
else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9)
|
break;
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8)
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
else if (ratio300 > 0.6)
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
else
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 1:
|
default:
|
||||||
{
|
score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
|
||||||
int totalHits = count50 + count100 + count300 + countMiss;
|
break;
|
||||||
score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1;
|
|
||||||
|
|
||||||
float ratio300 = (float)count300 / totalHits;
|
|
||||||
float ratio50 = (float)count50 / totalHits;
|
|
||||||
|
|
||||||
if (ratio300 == 1)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
|
|
||||||
else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
|
|
||||||
else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9)
|
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8)
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
else if (ratio300 > 0.6)
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
else
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
int totalHits = count50 + count100 + count300 + countMiss + countKatu;
|
|
||||||
score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1;
|
|
||||||
|
|
||||||
if (score.Accuracy == 1)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
|
|
||||||
else if (score.Accuracy > 0.98)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
|
|
||||||
else if (score.Accuracy > 0.94)
|
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
else if (score.Accuracy > 0.9)
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
else if (score.Accuracy > 0.85)
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
else
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
{
|
|
||||||
int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu;
|
|
||||||
score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1;
|
|
||||||
|
|
||||||
if (score.Accuracy == 1)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X;
|
|
||||||
else if (score.Accuracy > 0.95)
|
|
||||||
score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S;
|
|
||||||
else if (score.Accuracy > 0.9)
|
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
else if (score.Accuracy > 0.8)
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
else if (score.Accuracy > 0.7)
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
else
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void populateLazerAccuracyAndRank(ScoreInfo scoreInfo)
|
if (!score.IsLegacyScore)
|
||||||
{
|
return;
|
||||||
scoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(scoreInfo);
|
|
||||||
|
|
||||||
var rank = currentRuleset.CreateScoreProcessor().RankFromAccuracy(scoreInfo.Accuracy);
|
#pragma warning disable CS0618
|
||||||
|
// In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
|
||||||
|
// A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
|
||||||
|
var calculator = rulesetInstance.CreateDifficultyCalculator(workingBeatmap);
|
||||||
|
var attributes = calculator.Calculate(score.Mods);
|
||||||
|
|
||||||
foreach (var mod in scoreInfo.Mods.OfType<IApplicableToScoreProcessor>())
|
int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
|
||||||
rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
|
if (attributes.MaxCombo > maxComboFromStatistics)
|
||||||
|
score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
|
||||||
scoreInfo.Rank = rank;
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readLegacyReplay(Replay replay, StreamReader reader)
|
private void readLegacyReplay(Replay replay, StreamReader reader)
|
||||||
|
@ -43,9 +43,10 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
/// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct
|
/// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct
|
||||||
/// <see cref="LegacyRulesetExtensions.CalculateDifficultyPeppyStars"/> method. Reconvert all scores.
|
/// <see cref="LegacyRulesetExtensions.CalculateDifficultyPeppyStars"/> method. Reconvert all scores.
|
||||||
/// </description></item>
|
/// </description></item>
|
||||||
|
/// <item><description>30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public const int LATEST_VERSION = 30000012;
|
public const int LATEST_VERSION = 30000013;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
||||||
|
@ -17,7 +17,6 @@ using osu.Game.Scoring.Legacy;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -91,8 +90,6 @@ namespace osu.Game.Scoring
|
|||||||
ArgumentNullException.ThrowIfNull(model.BeatmapInfo);
|
ArgumentNullException.ThrowIfNull(model.BeatmapInfo);
|
||||||
ArgumentNullException.ThrowIfNull(model.Ruleset);
|
ArgumentNullException.ThrowIfNull(model.Ruleset);
|
||||||
|
|
||||||
PopulateMaximumStatistics(model);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(model.StatisticsJson))
|
if (string.IsNullOrEmpty(model.StatisticsJson))
|
||||||
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
||||||
|
|
||||||
@ -103,75 +100,6 @@ namespace osu.Game.Scoring
|
|||||||
// this requires: max combo, statistics, max statistics (where available), and mods to already be populated on the score.
|
// this requires: max combo, statistics, max statistics (where available), and mods to already be populated on the score.
|
||||||
if (StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised(model))
|
if (StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised(model))
|
||||||
model.TotalScore = StandardisedScoreMigrationTools.GetNewStandardised(model);
|
model.TotalScore = StandardisedScoreMigrationTools.GetNewStandardised(model);
|
||||||
else if (model.IsLegacyScore)
|
|
||||||
{
|
|
||||||
model.LegacyTotalScore = model.TotalScore;
|
|
||||||
StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="score">The score to populate the statistics of.</param>
|
|
||||||
public void PopulateMaximumStatistics(ScoreInfo score)
|
|
||||||
{
|
|
||||||
Debug.Assert(score.BeatmapInfo != null);
|
|
||||||
|
|
||||||
if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var beatmap = score.BeatmapInfo!.Detach();
|
|
||||||
var ruleset = score.Ruleset.Detach();
|
|
||||||
var rulesetInstance = ruleset.CreateInstance();
|
|
||||||
var scoreProcessor = rulesetInstance.CreateScoreProcessor();
|
|
||||||
|
|
||||||
Debug.Assert(rulesetInstance != null);
|
|
||||||
|
|
||||||
// Populate the maximum statistics.
|
|
||||||
HitResult maxBasicResult = rulesetInstance.GetHitResults()
|
|
||||||
.Select(h => h.result)
|
|
||||||
.Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
|
|
||||||
|
|
||||||
foreach ((HitResult result, int count) in score.Statistics)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case HitResult.LargeTickHit:
|
|
||||||
case HitResult.LargeTickMiss:
|
|
||||||
score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
|
||||||
case HitResult.SmallTickMiss:
|
|
||||||
score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HitResult.IgnoreHit:
|
|
||||||
case HitResult.IgnoreMiss:
|
|
||||||
case HitResult.SmallBonus:
|
|
||||||
case HitResult.LargeBonus:
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!score.IsLegacyScore)
|
|
||||||
return;
|
|
||||||
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
// In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
|
|
||||||
// A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
|
|
||||||
var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap));
|
|
||||||
var attributes = calculator.Calculate(score.Mods);
|
|
||||||
|
|
||||||
int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
|
|
||||||
if (attributes.MaxCombo > maxComboFromStatistics)
|
|
||||||
score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores).
|
// Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores).
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -26,6 +27,7 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
public class ScoreManager : ModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
|
public class ScoreManager : ModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
|
||||||
{
|
{
|
||||||
|
private readonly Func<BeatmapManager> beatmaps;
|
||||||
private readonly OsuConfigManager configManager;
|
private readonly OsuConfigManager configManager;
|
||||||
private readonly ScoreImporter scoreImporter;
|
private readonly ScoreImporter scoreImporter;
|
||||||
private readonly LegacyScoreExporter scoreExporter;
|
private readonly LegacyScoreExporter scoreExporter;
|
||||||
@ -44,6 +46,7 @@ namespace osu.Game.Scoring
|
|||||||
OsuConfigManager configManager = null)
|
OsuConfigManager configManager = null)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
|
this.beatmaps = beatmaps;
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
|
|
||||||
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
|
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
|
||||||
@ -171,7 +174,11 @@ namespace osu.Game.Scoring
|
|||||||
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score to populate the statistics of.</param>
|
/// <param name="score">The score to populate the statistics of.</param>
|
||||||
public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score);
|
public void PopulateMaximumStatistics(ScoreInfo score)
|
||||||
|
{
|
||||||
|
Debug.Assert(score.BeatmapInfo != null);
|
||||||
|
LegacyScoreDecoder.PopulateMaximumStatistics(score, beatmaps().GetWorkingBeatmap(score.BeatmapInfo.Detach()));
|
||||||
|
}
|
||||||
|
|
||||||
#region Implementation of IPresentImports<ScoreInfo>
|
#region Implementation of IPresentImports<ScoreInfo>
|
||||||
|
|
||||||
|
@ -29,10 +29,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// Set a divisor, updating the valid divisor range appropriately.
|
/// Set a divisor, updating the valid divisor range appropriately.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="divisor">The intended divisor.</param>
|
/// <param name="divisor">The intended divisor.</param>
|
||||||
public void SetArbitraryDivisor(int divisor)
|
/// <param name="preferKnownPresets">Forces changing the valid divisors to a known preset.</param>
|
||||||
|
public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false)
|
||||||
{
|
{
|
||||||
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
|
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
|
||||||
if (!ValidDivisors.Value.Presets.Contains(divisor))
|
if (preferKnownPresets || !ValidDivisors.Value.Presets.Contains(divisor))
|
||||||
{
|
{
|
||||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||||
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||||
|
@ -208,11 +208,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
switch (currentType)
|
switch (currentType)
|
||||||
{
|
{
|
||||||
case BeatDivisorType.Common:
|
case BeatDivisorType.Common:
|
||||||
beatDivisor.SetArbitraryDivisor(4);
|
beatDivisor.SetArbitraryDivisor(4, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BeatDivisorType.Triplets:
|
case BeatDivisorType.Triplets:
|
||||||
beatDivisor.SetArbitraryDivisor(6);
|
beatDivisor.SetArbitraryDivisor(6, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BeatDivisorType.Custom:
|
case BeatDivisorType.Custom:
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
bool selectionPerformed = performMouseDownActions(e);
|
bool selectionPerformed = performMouseDownActions(e);
|
||||||
bool movementPossible = prepareSelectionMovement();
|
bool movementPossible = prepareSelectionMovement(e);
|
||||||
|
|
||||||
// check if selection has occurred
|
// check if selection has occurred
|
||||||
if (selectionPerformed)
|
if (selectionPerformed)
|
||||||
@ -536,9 +536,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to begin the movement of any selected blueprints.
|
/// Attempts to begin the movement of any selected blueprints.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="e">The <see cref="MouseDownEvent"/> defining the beginning of a movement.</param>
|
||||||
/// <returns>Whether a movement is possible.</returns>
|
/// <returns>Whether a movement is possible.</returns>
|
||||||
private bool prepareSelectionMovement()
|
private bool prepareSelectionMovement(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!SelectionHandler.SelectedBlueprints.Any())
|
if (!SelectionHandler.SelectedBlueprints.Any())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
buttonArea.ButtonSystemState = state;
|
buttonArea.ButtonSystemState = state;
|
||||||
|
|
||||||
foreach (var b in buttonArea.Children.OfType<MainMenuButton>())
|
foreach (var b in buttonArea.OfType<MainMenuButton>())
|
||||||
b.ButtonSystemState = state;
|
b.ButtonSystemState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -43,8 +44,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private const double shoot_duration = 800;
|
private const double shoot_duration = 800;
|
||||||
|
|
||||||
protected override bool CanSpawnParticles => lastShootTime != null && Time.Current - lastShootTime < shoot_duration;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ISkinSource skin { get; set; } = null!;
|
private ISkinSource skin { get; set; } = null!;
|
||||||
|
|
||||||
@ -57,7 +56,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
private void load(TextureStore textures)
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("Menu/fountain-star") ?? textures.Get("Menu/fountain-star");
|
Texture = skin.GetTexture("Menu/fountain-star") ?? textures.Get("Menu/fountain-star");
|
||||||
Active.Value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override FallingParticle CreateParticle()
|
protected override FallingParticle CreateParticle()
|
||||||
@ -81,8 +79,15 @@ namespace osu.Game.Screens.Menu
|
|||||||
return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance);
|
return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate? deactivateDelegate;
|
||||||
|
|
||||||
public void Shoot(int direction)
|
public void Shoot(int direction)
|
||||||
{
|
{
|
||||||
|
Active.Value = true;
|
||||||
|
|
||||||
|
deactivateDelegate?.Cancel();
|
||||||
|
deactivateDelegate = Scheduler.AddDelayed(() => Active.Value = false, shoot_duration);
|
||||||
|
|
||||||
lastShootTime = Clock.CurrentTime;
|
lastShootTime = Clock.CurrentTime;
|
||||||
lastShootDirection = direction;
|
lastShootDirection = direction;
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
{
|
{
|
||||||
public override string Title => "Lounge";
|
public override string Title => "Lounge";
|
||||||
|
|
||||||
protected override bool PlayExitSound => false;
|
|
||||||
|
|
||||||
protected override BackgroundScreen CreateBackground() => new LoungeBackgroundScreen
|
protected override BackgroundScreen CreateBackground() => new LoungeBackgroundScreen
|
||||||
{
|
{
|
||||||
SelectedRoom = { BindTarget = SelectedRoom }
|
SelectedRoom = { BindTarget = SelectedRoom }
|
||||||
|
@ -44,8 +44,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
public override string ShortTitle => "room";
|
public override string ShortTitle => "room";
|
||||||
|
|
||||||
protected override bool PlayExitSound => !exitConfirmed;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerClient client { get; set; }
|
private MultiplayerClient client { get; set; }
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
public virtual string ShortTitle => Title;
|
public virtual string ShortTitle => Title;
|
||||||
|
|
||||||
|
protected sealed override bool PlayExitSound => false;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IRoomManager? RoomManager { get; private set; }
|
protected IRoomManager? RoomManager { get; private set; }
|
||||||
|
|
||||||
|
@ -223,7 +223,12 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
public override bool OnExiting(ScreenExitEvent e)
|
public override bool OnExiting(ScreenExitEvent e)
|
||||||
{
|
{
|
||||||
if (ValidForResume && PlayExitSound)
|
// Only play the exit sound if we are the last screen in the exit sequence.
|
||||||
|
// This stops many sample playbacks from stacking when a huge screen purge happens (ie. returning to menu via the home button
|
||||||
|
// from a deeply nested screen).
|
||||||
|
bool arrivingAtFinalDestination = e.Next == e.Destination;
|
||||||
|
|
||||||
|
if (ValidForResume && PlayExitSound && arrivingAtFinalDestination)
|
||||||
sampleExit?.Play();
|
sampleExit?.Play();
|
||||||
|
|
||||||
if (ValidForResume && logo != null)
|
if (ValidForResume && logo != null)
|
||||||
|
@ -78,8 +78,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
isPaused.Value = false;
|
isPaused.Value = false;
|
||||||
|
|
||||||
PrepareStart();
|
|
||||||
|
|
||||||
// The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time.
|
// The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time.
|
||||||
// Because we generally update our own current time quicker than children can query it (via Start/Seek/Update),
|
// Because we generally update our own current time quicker than children can query it (via Start/Seek/Update),
|
||||||
// this means that the first frame ever exposed to children may have a non-zero current time.
|
// this means that the first frame ever exposed to children may have a non-zero current time.
|
||||||
@ -99,14 +97,6 @@ namespace osu.Game.Screens.Play
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When <see cref="Start"/> is called, this will be run to give an opportunity to prepare the clock at the correct
|
|
||||||
/// start location.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void PrepareStart()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seek to a specific time in gameplay.
|
/// Seek to a specific time in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -42,35 +42,30 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Origin = anchor;
|
Origin = anchor;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
labelText = new OsuSpriteText
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
labelText = new OsuSpriteText
|
Alpha = 0,
|
||||||
|
Text = label.GetValueOrDefault(),
|
||||||
|
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
|
||||||
|
Margin = new MarginPadding { Left = 2.5f },
|
||||||
|
},
|
||||||
|
NumberContainer = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
|
||||||
Text = label.GetValueOrDefault(),
|
|
||||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
|
|
||||||
Margin = new MarginPadding { Left = 2.5f },
|
|
||||||
},
|
|
||||||
NumberContainer = new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
|
Anchor = anchor,
|
||||||
{
|
Origin = anchor,
|
||||||
Anchor = anchor,
|
},
|
||||||
Origin = anchor,
|
textPart = new ArgonCounterSpriteText(textLookup)
|
||||||
},
|
{
|
||||||
textPart = new ArgonCounterSpriteText(textLookup)
|
Anchor = anchor,
|
||||||
{
|
Origin = anchor,
|
||||||
Anchor = anchor,
|
},
|
||||||
Origin = anchor,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -110,7 +105,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true);
|
WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true);
|
||||||
ShowLabel.BindValueChanged(s => labelText.Alpha = s.NewValue ? 1 : 0, true);
|
ShowLabel.BindValueChanged(s =>
|
||||||
|
{
|
||||||
|
labelText.Alpha = s.NewValue ? 1 : 0;
|
||||||
|
NumberContainer.Y = s.NewValue ? 12 : 0;
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class ArgonCounterSpriteText : OsuSpriteText
|
private partial class ArgonCounterSpriteText : OsuSpriteText
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
public bool UsesFixedAnchor { get; set; }
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString();
|
protected override LocalisableString FormatCount(long count) => count.ToString();
|
||||||
|
|
||||||
protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
|
protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user