1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 14:53:01 +08:00

Merge remote-tracking branch 'refs/remotes/ppy/master' into beatmap-overlay-mod-selector-new

This commit is contained in:
Andrei Zavatski 2019-11-13 00:24:40 +03:00
commit 2719be7769
113 changed files with 2132 additions and 811 deletions

View File

@ -7,6 +7,12 @@
"commands": [
"dotnet-cake"
]
},
"dotnet-format": {
"version": "3.1.37601",
"commands": [
"dotnet-format"
]
}
}
}

View File

@ -12,16 +12,171 @@ trim_trailing_whitespace = true
#PascalCase for public and protected members
dotnet_naming_style.pascalcase.capitalization = pascal_case
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate
dotnet_naming_rule.public_members_pascalcase.severity = suggestion
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
dotnet_naming_rule.public_members_pascalcase.severity = error
dotnet_naming_rule.public_members_pascalcase.symbols = public_members
dotnet_naming_rule.public_members_pascalcase.style = pascalcase
#camelCase for private members
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_symbols.private_members.applicable_accessibilities = private
dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate
dotnet_naming_rule.private_members_camelcase.severity = suggestion
dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
dotnet_naming_rule.private_members_camelcase.severity = warning
dotnet_naming_rule.private_members_camelcase.symbols = private_members
dotnet_naming_rule.private_members_camelcase.style = camelcase
dotnet_naming_rule.private_members_camelcase.style = camelcase
dotnet_naming_symbols.local_function.applicable_kinds = local_function
dotnet_naming_rule.local_function_camelcase.severity = warning
dotnet_naming_rule.local_function_camelcase.symbols = local_function
dotnet_naming_rule.local_function_camelcase.style = camelcase
#all_lower for private and local constants/static readonlys
dotnet_naming_style.all_lower.capitalization = all_lower
dotnet_naming_style.all_lower.word_separator = _
dotnet_naming_symbols.private_constants.applicable_accessibilities = private
dotnet_naming_symbols.private_constants.required_modifiers = const
dotnet_naming_symbols.private_constants.applicable_kinds = field
dotnet_naming_rule.private_const_all_lower.severity = warning
dotnet_naming_rule.private_const_all_lower.symbols = private_constants
dotnet_naming_rule.private_const_all_lower.style = all_lower
dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
dotnet_naming_symbols.local_constants.applicable_kinds = local
dotnet_naming_symbols.local_constants.required_modifiers = const
dotnet_naming_rule.local_const_all_lower.severity = warning
dotnet_naming_rule.local_const_all_lower.symbols = local_constants
dotnet_naming_rule.local_const_all_lower.style = all_lower
#ALL_UPPER for non private constants/static readonlys
dotnet_naming_style.all_upper.capitalization = all_upper
dotnet_naming_style.all_upper.word_separator = _
dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_constants.required_modifiers = const
dotnet_naming_symbols.public_constants.applicable_kinds = field
dotnet_naming_rule.public_const_all_upper.severity = warning
dotnet_naming_rule.public_const_all_upper.symbols = public_constants
dotnet_naming_rule.public_const_all_upper.style = all_upper
dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
#Roslyn formating options
#Formatting - indentation options
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
#Formatting - new line options
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_open_brace = all
#csharp_new_line_before_members_in_anonymous_types = true
#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
csharp_new_line_between_query_expression_clauses = true
#Formatting - organize using options
dotnet_sort_system_directives_first = true
#Formatting - spacing options
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#Roslyn language styles
#Style - type names
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
csharp_style_var_when_type_is_apparent = true:none
csharp_style_var_for_built_in_types = true:none
csharp_style_var_elsewhere = true:silent
#Style - modifiers
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
#Style - parentheses
# Skipped because roslyn cannot separate +-*/ with << >>
#Style - expression bodies
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_operators = true:silent
csharp_style_expression_bodied_properties = true:silent
#Style - expression preferences
dotnet_style_object_initializer = true:warning
dotnet_style_collection_initializer = true:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_compound_assignment = true:silent
#Style - null/type checks
dotnet_style_coalesce_expression = true:warning
dotnet_style_null_propagation = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:silent
csharp_style_pattern_matching_over_as_with_null_check = true:silent
csharp_style_throw_expression = true:silent
csharp_style_conditional_delegate_call = true:suggestion
#Style - unused
dotnet_code_quality_unused_parameters = non_public:silent
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:silent
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:silent
csharp_style_deconstructed_variable_declaration = true:silent
#Style - other C# 7.x features
csharp_style_expression_bodied_local_functions = true:silent
dotnet_style_prefer_inferred_tuple_names = true:warning
csharp_prefer_simple_default_expression = true:warning
csharp_style_pattern_local_over_anonymous_function = true:silent
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Supressing roslyn built-in analyzers
# Suppress: EC112
#Field can be readonly
dotnet_diagnostic.IDE0044.severity = silent
#Private method is unused
dotnet_diagnostic.IDE0051.severity = silent
#Private member is unused
dotnet_diagnostic.IDE0052.severity = silent
#Rules for disposable
dotnet_diagnostic.IDE0067.severity = none
dotnet_diagnostic.IDE0068.severity = none
dotnet_diagnostic.IDE0069.severity = none

View File

@ -19,9 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
## Running osu!
@ -68,7 +68,7 @@ dotnet run --project osu.Desktop
If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
If the build fails, try to restore nuget packages with `dotnet restore`.
If the build fails, try to restore NuGet packages with `dotnet restore`.
### Testing with resource/framework modifications
@ -76,11 +76,11 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g
### Code analysis
Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice.
Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## Contributing
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
@ -88,7 +88,7 @@ Before starting, please make sure you are familiar with the [development and tes
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
## Licence

View File

@ -11,6 +11,7 @@ var target = Argument("target", "Build");
var configuration = Argument("configuration", "Release");
var rootDirectory = new DirectoryPath("..");
var sln = rootDirectory.CombineWithFilePath("osu.sln");
var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj");
var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
@ -60,8 +61,12 @@ Task("CodeFileSanity")
});
});
Task("DotnetFormat")
.Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check"));
Task("Build")
.IsDependentOn("CodeFileSanity")
.IsDependentOn("DotnetFormat")
.IsDependentOn("InspectCode")
.IsDependentOn("Test");

View File

@ -49,12 +49,12 @@ desc 'Deploy to play store'
desc 'Compile the project'
lane :build do |options|
nuget_restore(
project_path: 'osu.Android.sln'
project_path: 'osu.sln'
)
souyuz(
build_configuration: 'Release',
solution_path: 'osu.Android.sln',
solution_path: 'osu.sln',
platform: "android",
output_path: "osu.Android/bin/Release/",
keystore_path: options[:keystore_path],
@ -70,7 +70,7 @@ desc 'Deploy to play store'
android_build = split.join('')
app_version(
solution_path: 'osu.Android.sln',
solution_path: 'osu.sln',
version: options[:version],
build: android_build,
)
@ -106,7 +106,7 @@ platform :ios do
desc 'Compile the project'
lane :build do
nuget_restore(
project_path: 'osu.iOS.sln'
project_path: 'osu.sln'
)
souyuz(

View File

@ -53,6 +53,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1108.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1112.0" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -40,8 +40,10 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
for (int i = 0; i < 512; i++)
{
if (i % 5 < 3)
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
}
return beatmap;
}

View File

@ -195,10 +195,15 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
if (currentObject is Fruit)
objectWithDroplets.Add(currentObject);
if (currentObject is JuiceStream)
{
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
{
if (!(currentJuiceElement is TinyDroplet))
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
}
}
}
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));

View File

@ -27,11 +27,13 @@ namespace osu.Game.Rulesets.Catch.Objects
return;
for (double i = StartTime; i <= EndTime; i += spacing)
{
AddNested(new Banana
{
Samples = Samples,
StartTime = i
});
}
}
public double EndTime => StartTime + Duration;

View File

@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance => Path.Distance;
public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
public double? LegacyLastTickOffset { get; set; }
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private List<HitSampleInfo> sampleInfoListAt(double time)
private IList<HitSampleInfo> sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;

View File

@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private List<HitSampleInfo> sampleInfoListAt(double time)
private IList<HitSampleInfo> sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;

View File

@ -109,8 +109,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
// Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++)
{
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
}
return pattern;
}
@ -132,8 +134,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
// Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++)
{
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
}
return pattern;
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -24,12 +24,14 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneDrawableJudgement()
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
{
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}));
}
}
}
}

View File

@ -29,8 +29,10 @@ namespace osu.Game.Rulesets.Osu.Tests
};
for (int i = 0; i < 512; i++)
{
if (i % 32 < 20)
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
}
return beatmap;
}

View File

@ -126,6 +126,67 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
}
[Test]
public void TestChangeSamplesWithNoNodeSamples()
{
DrawableSlider slider = null;
AddStep("create slider", () =>
{
slider = (DrawableSlider)createSlider(repeats: 1);
Add(slider);
});
AddStep("change samples", () => slider.HitObject.Samples = new[]
{
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
});
AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
bool assertSamples(HitObject hitObject)
{
return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)
&& hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE);
}
}
[Test]
public void TestChangeSamplesWithNodeSamples()
{
DrawableSlider slider = null;
AddStep("create slider", () =>
{
slider = (DrawableSlider)createSlider(repeats: 1);
for (int i = 0; i < 2; i++)
((Slider)slider.HitObject).NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
Add(slider);
});
AddStep("change samples", () => slider.HitObject.Samples = new[]
{
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
});
AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
}
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
@ -143,7 +204,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(52, -34)
}, 700),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats),
StackHeight = 10
};
@ -174,7 +234,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(distance, 0),
}, distance),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats),
StackHeight = stackHeight
};
@ -194,7 +253,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(400, 0)
}, 600),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@ -218,7 +276,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(430, 0)
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@ -241,7 +298,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(430, 0)
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@ -265,7 +321,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(0, -200)
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@ -275,7 +330,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable createCatmull(int repeats = 0)
{
var repeatSamples = new List<List<HitSampleInfo>>();
var repeatSamples = new List<IList<HitSampleInfo>>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<HitSampleInfo>());
@ -297,14 +352,6 @@ namespace osu.Game.Rulesets.Osu.Tests
return createDrawable(slider, 3, 1);
}
private List<List<HitSampleInfo>> createEmptySamples(int repeats)
{
var repeatSamples = new List<List<HitSampleInfo>>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<HitSampleInfo>());
return repeatSamples;
}
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{
var cpi = new ControlPointInfo();

View File

@ -1,8 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@ -10,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
@ -44,6 +51,78 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.UpdateFrom(HitObject);
}
private Vector2 rightClickPosition;
protected override bool OnMouseDown(MouseDownEvent e)
{
switch (e.Button)
{
case MouseButton.Right:
rightClickPosition = e.MouseDownPosition;
return false; // Allow right click to be handled by context menu
case MouseButton.Left when e.ControlPressed && IsSelected:
placementControlPointIndex = addControlPoint(e.MousePosition);
return true; // Stop input from being handled and modifying the selection
}
return false;
}
private int? placementControlPointIndex;
protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null;
protected override bool OnDrag(DragEvent e)
{
Debug.Assert(placementControlPointIndex != null);
Vector2 position = e.MousePosition - HitObject.Position;
var controlPoints = HitObject.Path.ControlPoints.ToArray();
controlPoints[placementControlPointIndex.Value] = position;
onNewControlPoints(controlPoints);
return true;
}
protected override bool OnDragEnd(DragEndEvent e)
{
placementControlPointIndex = null;
return true;
}
private int addControlPoint(Vector2 position)
{
position -= HitObject.Position;
var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1];
HitObject.Path.ControlPoints.CopyTo(controlPoints);
int insertionIndex = 0;
float minDistance = float.MaxValue;
for (int i = 0; i < controlPoints.Length - 2; i++)
{
float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position);
if (dist < minDistance)
{
insertionIndex = i + 1;
minDistance = dist;
}
}
// Move the control points from the insertion index onwards to make room for the insertion
Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1);
controlPoints[insertionIndex] = position;
onNewControlPoints(controlPoints);
return insertionIndex;
}
private void onNewControlPoints(Vector2[] controlPoints)
{
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
@ -54,6 +133,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
UpdateHitObject();
}
public override MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
};
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osuTK;
@ -12,11 +16,36 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
/// <summary>
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
/// </summary>
private const double editor_hit_object_fade_out_extension = 500;
public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods)
{
}
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h)
=> base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState);
private void updateState(DrawableHitObject hitObject, ArmedState state)
{
switch (state)
{
case ArmedState.Miss:
// Get the existing fade out transform
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
if (existing == null)
return;
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
break;
}
}
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One };

View File

@ -55,8 +55,10 @@ namespace osu.Game.Rulesets.Osu.Mods
}
for (int i = 0; i < amountWiggles; i++)
{
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
wiggle();
}
// Keep wiggling sliders and spinners for their duration
if (!(osuObject is IHasEndTime endTime))
@ -65,8 +67,10 @@ namespace osu.Game.Rulesets.Osu.Mods
amountWiggles = (int)(endTime.Duration / wiggle_duration);
for (int i = 0; i < amountWiggles; i++)
{
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
wiggle();
}
}
}
}

View File

@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
internal float LazyTravelDistance;
public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
private int repeatCount;
@ -108,6 +108,12 @@ namespace osu.Game.Rulesets.Osu.Objects
public HitCircle HeadCircle;
public SliderTailCircle TailCircle;
public Slider()
{
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
}
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@ -128,18 +134,6 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var e in
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<HitSampleInfo>();
if (firstSample != null)
sampleList.Add(new HitSampleInfo
{
Bank = firstSample.Bank,
Volume = firstSample.Volume,
Name = @"slidertick",
});
switch (e.Type)
{
case SliderEventType.Tick:
@ -151,7 +145,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,
Scale = Scale,
Samples = sampleList
});
break;
@ -161,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = e.Time,
Position = Position,
StackHeight = StackHeight,
Samples = getNodeSamples(0),
SampleControlPoint = SampleControlPoint,
});
break;
@ -187,11 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,
Scale = Scale,
Samples = getNodeSamples(e.SpanIndex + 1)
});
break;
}
}
updateNestedSamples();
}
private void updateNestedPositions()
@ -203,7 +196,33 @@ namespace osu.Game.Rulesets.Osu.Objects
TailCircle.Position = EndPosition;
}
private List<HitSampleInfo> getNodeSamples(int nodeIndex) =>
private void updateNestedSamples()
{
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<HitSampleInfo>();
if (firstSample != null)
{
sampleList.Add(new HitSampleInfo
{
Bank = firstSample.Bank,
Volume = firstSample.Volume,
Name = @"slidertick",
});
}
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
tick.Samples = sampleList;
foreach (var repeat in NestedHitObjects.OfType<RepeatPoint>())
repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
if (HeadCircle != null)
HeadCircle.Samples = getNodeSamples(0);
}
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information
List<HitSampleInfo> samples = obj.Samples;
IList<HitSampleInfo> samples = obj.Samples;
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
@ -117,13 +117,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
List<List<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<List<HitSampleInfo>>(new[] { samples });
List<IList<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
List<HitSampleInfo> currentSamples = allSamples[i];
IList<HitSampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -225,8 +225,10 @@ namespace osu.Game.Tests.NonVisual
private void fastForwardToPoint(double destination)
{
for (int i = 0; i < 1000; i++)
{
if (handler.SetFrameFromTime(destination) == null)
return;
}
throw new TimeoutException("Seek was never fulfilled");
}

View File

@ -25,7 +25,9 @@ namespace osu.Game.Tests.Skins
var comboColors = decoder.Decode(stream).ComboColours;
List<Color4> expectedColors;
if (hasColours)
{
expectedColors = new List<Color4>
{
new Color4(142, 199, 255, 255),
@ -33,6 +35,7 @@ namespace osu.Game.Tests.Skins
new Color4(128, 255, 255, 255),
new Color4(100, 100, 100, 100),
};
}
else
expectedColors = new DefaultSkin().Configuration.ComboColours;

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
@ -59,6 +60,9 @@ namespace osu.Game.Tests.Visual.Components
AddStep("start track 2", () => track2.Start());
AddAssert("track 1 stopped", () => !track1.IsRunning);
AddAssert("track 2 started", () => track2.IsRunning);
AddStep("start track 1", () => track1.Start());
AddAssert("track 2 stopped", () => !track2.IsRunning);
AddAssert("track 1 started", () => track1.IsRunning);
}
[Test]
@ -88,9 +92,25 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("stopped", () => !track.IsRunning);
}
private PreviewTrack getTrack() => trackManager.Get(null);
[Test]
public void TestNonPresentTrack()
{
TestPreviewTrack track = null;
private PreviewTrack getOwnedTrack()
AddStep("get non-present track", () =>
{
Add(new TestTrackOwner(track = getTrack()));
track.Alpha = 0;
});
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("seek to end", () => track.Track.Seek(track.Track.Length));
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
@ -122,14 +142,16 @@ namespace osu.Game.Tests.Visual.Components
}
}
private class TestPreviewTrackManager : PreviewTrackManager
public class TestPreviewTrackManager : PreviewTrackManager
{
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
protected class TestPreviewTrack : TrackManagerPreviewTrack
public class TestPreviewTrack : TrackManagerPreviewTrack
{
private readonly ITrackStore trackManager;
public new Track Track => base.Track;
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
: base(beatmapSetInfo, trackManager)
{

View File

@ -130,12 +130,14 @@ namespace osu.Game.Tests.Visual.Online
AddRepeatStep("add many messages", () =>
{
for (int i = 0; i < messages_per_call; i++)
{
testChannel.AddNewMessages(new Message(sequence++)
{
Sender = longUsernameUser,
Content = "Many messages! " + Guid.NewGuid(),
Timestamp = DateTimeOffset.Now
});
}
}, Channel.MAX_HISTORY / messages_per_call + 5);
AddAssert("Ensure no adjacent day separators", () =>
@ -143,8 +145,10 @@ namespace osu.Game.Tests.Visual.Online
var indices = chatDisplay.FillFlow.OfType<DrawableChannel.DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
foreach (var i in indices)
{
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator)
return false;
}
return true;
});

View File

@ -467,8 +467,10 @@ namespace osu.Game.Tests.Visual.SongSelect
private void advanceSelection(bool diff, int direction = 1, int count = 1)
{
if (count == 1)
{
AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff));
}
else
{
AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>

View File

@ -132,11 +132,13 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(1);
if (rulesetsInSameBeatmap)
{
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait();
});
}
else
{
addRulesetImportStep(1);

View File

@ -109,16 +109,20 @@ namespace osu.Game.Tests.Visual
AddAssert("check OsuGame DI members", () =>
{
foreach (var type in requiredGameDependencies)
{
if (game.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached");
}
return true;
});
AddAssert("check OsuGameBase DI members", () =>
{
foreach (var type in requiredGameBaseDependencies)
{
if (gameBase.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached");
}
return true;
});

View File

@ -0,0 +1,145 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneStatefulMenuItem : ManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuMenu),
typeof(StatefulMenuItem),
typeof(TernaryStateMenuItem),
typeof(DrawableStatefulMenuItem),
};
[Test]
public void TestTernaryMenuItem()
{
OsuMenu menu = null;
Bindable<TernaryState> state = new Bindable<TernaryState>(TernaryState.Indeterminate);
AddStep("create menu", () =>
{
state.Value = TernaryState.Indeterminate;
Child = menu = new OsuMenu(Direction.Vertical, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
new TernaryStateMenuItem("First"),
new TernaryStateMenuItem("Second") { State = { BindTarget = state } },
new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } },
}
};
});
checkState(TernaryState.Indeterminate);
click();
checkState(TernaryState.True);
click();
checkState(TernaryState.False);
click();
checkState(TernaryState.True);
click();
checkState(TernaryState.False);
AddStep("change state via bindable", () => state.Value = TernaryState.True);
void click() =>
AddStep("click", () =>
{
InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre);
InputManager.Click(MouseButton.Left);
});
void checkState(TernaryState expected)
=> AddAssert($"state is {expected}", () => state.Value == expected);
}
[Test]
public void TestCustomState()
{
AddStep("create menu", () =>
{
Child = new OsuMenu(Direction.Vertical, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
new TestMenuItem("First", MenuItemType.Standard, getNextState),
new TestMenuItem("Second", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State2 } },
new TestMenuItem("Third", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State3 } },
}
};
});
}
private TestStates getNextState(TestStates state)
{
switch (state)
{
case TestStates.State1:
return TestStates.State2;
case TestStates.State2:
return TestStates.State3;
case TestStates.State3:
return TestStates.State1;
}
return TestStates.State1;
}
private class TestMenuItem : StatefulMenuItem<TestStates>
{
public TestMenuItem(string text, MenuItemType type, Func<TestStates, TestStates> changeStateFunc)
: base(text, changeStateFunc, type)
{
}
public override IconUsage? GetIconForState(TestStates state)
{
switch (state)
{
case TestStates.State1:
return FontAwesome.Solid.DiceOne;
case TestStates.State2:
return FontAwesome.Solid.DiceTwo;
case TestStates.State3:
return FontAwesome.Solid.DiceThree;
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}
}
}
private enum TestStates
{
State1,
State2,
State3
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneToggleMenuItem : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuMenu),
typeof(ToggleMenuItem),
typeof(DrawableStatefulMenuItem)
};
public TestSceneToggleMenuItem()
{
Add(new OsuMenu(Direction.Vertical, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Items = new[]
{
new ToggleMenuItem("First"),
new ToggleMenuItem("Second") { State = { Value = true } }
}
});
}
}
}

View File

@ -102,7 +102,8 @@ namespace osu.Game.Tournament.Tests.Components
Content = "Okay okay, calm down guys. Let's do this!"
}));
AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId())
AddStep("multiple messages", () => testChannel.AddNewMessages(
new Message(nextMessageId())
{
Sender = admin,
Content = "I spam you!"

View File

@ -131,6 +131,7 @@ namespace osu.Game.Tournament.Components
});
if (!string.IsNullOrEmpty(mods))
{
AddInternal(new Sprite
{
Texture = textures.Get($"mods/{mods}"),
@ -139,6 +140,7 @@ namespace osu.Game.Tournament.Components
Margin = new MarginPadding(20),
Scale = new Vector2(0.5f)
});
}
}
private void matchChanged(ValueChangedEvent<TournamentMatch> match)

View File

@ -29,12 +29,14 @@ namespace osu.Game.Tournament.Components
};
}
else
{
InternalChild = video = new VideoSprite(stream)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Clock = new FramedClock(manualClock = new ManualClock())
};
}
}
public bool Loop

View File

@ -60,6 +60,7 @@ namespace osu.Game.Tournament.IPC
const string file_ipc_channel_filename = "ipc-channel.txt";
if (Storage.Exists(file_ipc_filename))
{
scheduled = Scheduler.AddDelayed(delegate
{
try
@ -134,6 +135,7 @@ namespace osu.Game.Tournament.IPC
// file might be in use.
}
}, 250, true);
}
}
catch (Exception e)
{

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Rulesets;
namespace osu.Game.Tournament.Models
{
@ -14,6 +15,8 @@ namespace osu.Game.Tournament.Models
[Serializable]
public class LadderInfo
{
public Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
public BindableList<TournamentMatch> Matches = new BindableList<TournamentMatch>();
public BindableList<TournamentRound> Rounds = new BindableList<TournamentRound>();
public BindableList<TournamentTeam> Teams = new BindableList<TournamentTeam>();

View File

@ -266,12 +266,14 @@ namespace osu.Game.Tournament.Screens.Editors
drawableContainer.Clear();
if (Model.BeatmapInfo != null)
{
drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 300
};
}
}
}
}

View File

@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
@ -24,7 +23,7 @@ namespace osu.Game.Tournament.Screens.Editors
public class TeamEditorScreen : TournamentEditorScreen<TeamEditorScreen.TeamRow, TournamentTeam>
{
[Resolved]
private Framework.Game game { get; set; }
private TournamentGameBase game { get; set; }
protected override BindableList<TournamentTeam> Storage => LadderInfo.Teams;
@ -198,6 +197,9 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
protected IAPIProvider API { get; private set; }
[Resolved]
private TournamentGameBase game { get; set; }
private readonly Bindable<string> userId = new Bindable<string>();
private readonly Container drawableContainer;
@ -280,25 +282,7 @@ namespace osu.Game.Tournament.Screens.Editors
return;
}
var req = new GetUserRequest(user.Id);
req.Success += res =>
{
// TODO: this should be done in a better way.
user.Username = res.Username;
user.Country = res.Country;
user.Cover = res.Cover;
updatePanel();
};
req.Failure += _ =>
{
user.Id = 1;
updatePanel();
};
API.Queue(req);
game.PopulateUser(user, updatePanel, updatePanel);
}, true);
}

View File

@ -81,8 +81,10 @@ namespace osu.Game.Tournament.Screens.Ladder
LadderInfo.Matches.ItemsRemoved += matches =>
{
foreach (var p in matches)
foreach (var d in MatchesContainer.Where(d => d.Match == p))
d.Expire();
{
foreach (var d in MatchesContainer.Where(d => d.Match == p))
d.Expire();
}
layout.Invalidate();
};

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -10,6 +11,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Tournament.IPC;
using osuTK;
using osuTK.Graphics;
@ -28,6 +30,9 @@ namespace osu.Game.Tournament.Screens
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
[BackgroundDependencyLoader]
private void load()
{
@ -85,7 +90,34 @@ namespace osu.Game.Tournament.Screens
Value = api?.LocalUser.Value.Username,
Failing = api?.IsLoggedIn != true,
Description = "In order to access the API and display metadata, a login is required."
}
},
new LabelledDropdown<RulesetInfo>
{
Label = "Ruleset",
Description = "Decides what stats are displayed and which ranks are retrieved for players",
Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset,
},
};
}
public class LabelledDropdown<T> : LabelledComponent<OsuDropdown<T>, T>
{
public LabelledDropdown()
: base(true)
{
}
public IEnumerable<T> Items
{
get => Component.Items;
set => Component.Items = value;
}
protected override OsuDropdown<T> CreateComponent() => new OsuDropdown<T>
{
RelativeSizeAxes = Axes.X,
Width = 0.5f,
};
}

View File

@ -164,6 +164,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
if (team != null)
{
foreach (var p in team.Players)
{
players.Add(new OsuSpriteText
{
Text = p.Username,
@ -172,6 +173,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = left ? Anchor.CentreRight : Anchor.CentreLeft,
});
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Drawing;
using System.IO;
using System.Linq;
@ -21,11 +22,13 @@ using osu.Game.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Users;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tournament
{
[Cached(typeof(TournamentGameBase))]
public abstract class TournamentGameBase : OsuGameBase
{
private const string bracket_filename = "bracket.json";
@ -126,6 +129,8 @@ namespace osu.Game.Tournament
ladder = new LadderInfo();
}
Ruleset.BindTo(ladder.Ruleset);
dependencies.Cache(ladder);
bool addedInfo = false;
@ -164,15 +169,17 @@ namespace osu.Game.Tournament
// link matches to rounds
foreach (var round in ladder.Rounds)
foreach (var id in round.Matches)
{
var found = ladder.Matches.FirstOrDefault(p => p.ID == id);
if (found != null)
foreach (var id in round.Matches)
{
found.Round.Value = round;
if (round.StartDate.Value > found.Date.Value)
found.Date.Value = round.StartDate.Value;
var found = ladder.Matches.FirstOrDefault(p => p.ID == id);
if (found != null)
{
found.Round.Value = round;
if (round.StartDate.Value > found.Date.Value)
found.Date.Value = round.StartDate.Value;
}
}
}
@ -192,15 +199,13 @@ namespace osu.Game.Tournament
bool addedInfo = false;
foreach (var t in ladder.Teams)
foreach (var p in t.Players)
if (string.IsNullOrEmpty(p.Username))
{
foreach (var p in t.Players)
{
var req = new GetUserRequest(p.Id);
req.Perform(API);
p.Username = req.Result.Username;
PopulateUser(p);
addedInfo = true;
}
}
return addedInfo;
}
@ -213,19 +218,47 @@ namespace osu.Game.Tournament
bool addedInfo = false;
foreach (var r in ladder.Rounds)
foreach (var b in r.Beatmaps)
if (b.BeatmapInfo == null && b.ID > 0)
{
foreach (var b in r.Beatmaps)
{
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
req.Perform(API);
b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
if (b.BeatmapInfo == null && b.ID > 0)
{
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
req.Perform(API);
b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
addedInfo = true;
addedInfo = true;
}
}
}
return addedInfo;
}
public void PopulateUser(User user, Action success = null, Action failure = null)
{
var req = new GetUserRequest(user.Id, Ruleset.Value);
req.Success += res =>
{
user.Username = res.Username;
user.Statistics = res.Statistics;
user.Username = res.Username;
user.Country = res.Country;
user.Cover = res.Cover;
success?.Invoke();
};
req.Failure += _ =>
{
user.Id = 1;
failure?.Invoke();
};
API.Queue(req);
}
protected override void LoadComplete()
{
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display

View File

@ -24,36 +24,37 @@ namespace osu.Game.Audio
/// </summary>
public event Action Started;
private Track track;
protected Track Track { get; private set; }
private bool hasStarted;
[BackgroundDependencyLoader]
private void load()
{
track = GetTrack();
if (track != null)
track.Completed += Stop;
Track = GetTrack();
if (Track != null)
Track.Completed += Stop;
}
/// <summary>
/// Length of the track.
/// </summary>
public double Length => track?.Length ?? 0;
public double Length => Track?.Length ?? 0;
/// <summary>
/// The current track time.
/// </summary>
public double CurrentTime => track?.CurrentTime ?? 0;
public double CurrentTime => Track?.CurrentTime ?? 0;
/// <summary>
/// Whether the track is loaded.
/// </summary>
public bool TrackLoaded => track?.IsLoaded ?? false;
public bool TrackLoaded => Track?.IsLoaded ?? false;
/// <summary>
/// Whether the track is playing.
/// </summary>
public bool IsRunning => track?.IsRunning ?? false;
public bool IsRunning => Track?.IsRunning ?? false;
private ScheduledDelegate startDelegate;
@ -63,7 +64,7 @@ namespace osu.Game.Audio
/// <returns>Whether the track is started or already playing.</returns>
public bool Start()
{
if (track == null)
if (Track == null)
return false;
startDelegate = Schedule(() =>
@ -73,7 +74,7 @@ namespace osu.Game.Audio
hasStarted = true;
track.Restart();
Track.Restart();
Started?.Invoke();
});
@ -87,7 +88,7 @@ namespace osu.Game.Audio
{
startDelegate?.Cancel();
if (track == null)
if (Track == null)
return;
if (!hasStarted)
@ -95,7 +96,7 @@ namespace osu.Game.Audio
hasStarted = false;
track.Stop();
Track.Stop();
Stopped?.Invoke();
}

View File

@ -55,6 +55,9 @@ namespace osu.Game.Audio
track.Stopped += () => Schedule(() =>
{
if (current != track)
return;
current = null;
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
});
@ -85,7 +88,7 @@ namespace osu.Game.Audio
/// </summary>
protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore);
protected class TrackManagerPreviewTrack : PreviewTrack
public class TrackManagerPreviewTrack : PreviewTrack
{
public IPreviewTrackOwner Owner { get; private set; }

View File

@ -144,16 +144,16 @@ namespace osu.Game.Beatmaps.Formats
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
}
break;
}
case "L":
{
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
var loopCount = int.Parse(split[2]);
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
}
break;
}
default:
{
@ -171,16 +171,16 @@ namespace osu.Game.Beatmaps.Formats
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
}
break;
}
case "S":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
}
break;
}
case "V":
{
@ -189,16 +189,16 @@ namespace osu.Game.Beatmaps.Formats
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
}
break;
}
case "R":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
}
break;
}
case "M":
{
@ -208,24 +208,24 @@ namespace osu.Game.Beatmaps.Formats
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
}
break;
}
case "MX":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
}
break;
}
case "MY":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
}
break;
}
case "C":
{
@ -238,8 +238,8 @@ namespace osu.Game.Beatmaps.Formats
timelineGroup?.Colour.Add(easing, startTime, endTime,
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
}
break;
}
case "P":
{
@ -259,14 +259,16 @@ namespace osu.Game.Beatmaps.Formats
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
break;
}
}
break;
}
default:
throw new InvalidDataException($@"Unknown command type: {commandType}");
}
}
break;
}
}
}
}

View File

@ -133,8 +133,10 @@ namespace osu.Game.Beatmaps
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
foreach (var mod in mods.OfType<IApplicableToHitObject>())
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
{
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
}
processor?.PostProcess();

View File

@ -264,9 +264,12 @@ namespace osu.Game.Database
{
// for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith)))
{
using (Stream s = reader.GetStream(file))
s.CopyTo(hashable);
}
return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null;
}
@ -485,12 +488,16 @@ namespace osu.Game.Database
// import files to manager
foreach (string file in reader.Filenames)
{
using (Stream s = reader.GetStream(file))
{
fileInfos.Add(new TFileModel
{
Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)),
FileInfo = files.Add(s)
});
}
}
return fileInfos;
}
@ -651,8 +658,10 @@ namespace osu.Game.Database
private void handleEvent(Action a)
{
if (delayingEvents)
{
lock (queuedEvents)
queuedEvents.Add(a);
}
else
a.Invoke();
}

View File

@ -43,9 +43,11 @@ namespace osu.Game.Graphics.Containers
// if we don't have enough time for the second shake, skip it.
if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4)
{
sequence = sequence
.MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then()
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
}
sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine);
}

View File

@ -0,0 +1,133 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public class DrawableOsuMenuItem : Menu.DrawableMenuItem
{
public const int MARGIN_HORIZONTAL = 17;
public const int MARGIN_VERTICAL = 4;
private const int text_size = 17;
private const int transition_length = 80;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private TextContainer text;
public DrawableOsuMenuItem(MenuItem item)
: base(item)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Samples.Get(@"UI/generic-hover");
sampleClick = audio.Samples.Get(@"UI/generic-select");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch ((Item as OsuMenuItem)?.Type)
{
default:
case MenuItemType.Standard:
text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(HoverEvent e)
{
sampleHover.Play();
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(e);
}
protected override bool OnClick(ClickEvent e)
{
sampleClick.Play();
return base.OnClick(e);
}
protected sealed override Drawable CreateContent() => text = CreateTextContainer();
protected virtual TextContainer CreateTextContainer() => new TextContainer();
protected class TextContainer : Container, IHasText
{
public string Text
{
get => NormalText.Text;
set
{
NormalText.Text = value;
BoldText.Text = value;
}
}
public readonly SpriteText NormalText;
public readonly SpriteText BoldText;
public TextContainer()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
NormalText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: text_size),
Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL },
},
BoldText = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold),
Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL },
}
};
}
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
namespace osu.Game.Graphics.UserInterface
{
public class DrawableStatefulMenuItem : DrawableOsuMenuItem
{
protected new StatefulMenuItem Item => (StatefulMenuItem)base.Item;
public DrawableStatefulMenuItem(StatefulMenuItem item)
: base(item)
{
}
protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item);
private class ToggleTextContainer : TextContainer
{
private readonly StatefulMenuItem menuItem;
private readonly Bindable<object> state;
private readonly SpriteIcon stateIcon;
public ToggleTextContainer(StatefulMenuItem menuItem)
{
this.menuItem = menuItem;
state = menuItem.State.GetBoundCopy();
Add(stateIcon = new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(10),
Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL },
AlwaysPresent = true,
});
}
protected override void LoadComplete()
{
base.LoadComplete();
state.BindValueChanged(updateState, true);
}
protected override void Update()
{
base.Update();
// Todo: This is bad. This can maybe be done better with a refactor of DrawableOsuMenuItem.
stateIcon.X = BoldText.DrawWidth + 10;
}
private void updateState(ValueChangedEvent<object> state)
{
var icon = menuItem.GetIconForState(state.NewValue);
if (icon == null)
stateIcon.Alpha = 0;
else
{
stateIcon.Alpha = 1;
stateIcon.Icon = icon.Value;
}
}
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface
{
@ -35,5 +36,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
protected override Menu CreateSubMenu() => new OsuContextMenu();
}
}

View File

@ -1,18 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Graphics.UserInterface
@ -45,7 +39,16 @@ namespace osu.Game.Graphics.UserInterface
}
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item);
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item)
{
switch (item)
{
case StatefulMenuItem stateful:
return new DrawableStatefulMenuItem(stateful);
}
return new DrawableOsuMenuItem(item);
}
protected override ScrollContainer<Drawable> CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction);
@ -53,122 +56,5 @@ namespace osu.Game.Graphics.UserInterface
{
Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight
};
protected class DrawableOsuMenuItem : DrawableMenuItem
{
private const int margin_horizontal = 17;
private const int text_size = 17;
private const int transition_length = 80;
public const int MARGIN_VERTICAL = 4;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private TextContainer text;
public DrawableOsuMenuItem(MenuItem item)
: base(item)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Samples.Get(@"UI/generic-hover");
sampleClick = audio.Samples.Get(@"UI/generic-select");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch ((Item as OsuMenuItem)?.Type)
{
default:
case MenuItemType.Standard:
text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(HoverEvent e)
{
sampleHover.Play();
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(e);
}
protected override bool OnClick(ClickEvent e)
{
sampleClick.Play();
return base.OnClick(e);
}
protected sealed override Drawable CreateContent() => text = CreateTextContainer();
protected virtual TextContainer CreateTextContainer() => new TextContainer();
protected class TextContainer : Container, IHasText
{
public string Text
{
get => NormalText.Text;
set
{
NormalText.Text = value;
BoldText.Text = value;
}
}
public readonly SpriteText NormalText;
public readonly SpriteText BoldText;
public TextContainer()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
NormalText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: text_size),
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
BoldText = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold),
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
};
}
}
}
}
}

View File

@ -11,9 +11,8 @@ namespace osu.Game.Graphics.UserInterface
public readonly MenuItemType Type;
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: base(text)
: this(text, type, null)
{
Type = type;
}
public OsuMenuItem(string text, MenuItemType type, Action action)

View File

@ -51,8 +51,10 @@ namespace osu.Game.Graphics.UserInterface
});
if (isEnumType && AddEnumEntriesAutomatically)
{
foreach (var val in (T[])Enum.GetValues(typeof(T)))
AddItem(val);
}
}
[BackgroundDependencyLoader]

View File

@ -43,9 +43,12 @@ namespace osu.Game.Graphics.UserInterface
protected override string FormatCount(double count)
{
string format = new string('0', (int)LeadingZeroes);
if (UseCommaSeparator)
{
for (int i = format.Length - 3; i > 0; i -= 3)
format = format.Insert(i, @",");
}
return ((long)count).ToString(format);
}

View File

@ -0,0 +1,105 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// An <see cref="OsuMenuItem"/> which contains and displays a state.
/// </summary>
public abstract class StatefulMenuItem : OsuMenuItem
{
/// <summary>
/// The current state that should be displayed.
/// </summary>
public readonly Bindable<object> State = new Bindable<object>();
/// <summary>
/// Creates a new <see cref="StatefulMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard)
: this(text, changeStateFunc, type, null)
{
}
/// <summary>
/// Creates a new <see cref="StatefulMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type, Action<object> action)
: base(text, type)
{
Action.Value = () =>
{
State.Value = changeStateFunc?.Invoke(State.Value) ?? State.Value;
action?.Invoke(State.Value);
};
}
/// <summary>
/// Retrieves the icon to be displayed for a state.
/// </summary>
/// <param name="state">The state to retrieve the relevant icon for.</param>
/// <returns>The icon to be displayed for <paramref name="state"/>.</returns>
public abstract IconUsage? GetIconForState(object state);
}
public abstract class StatefulMenuItem<T> : StatefulMenuItem
where T : struct
{
/// <summary>
/// The current state that should be displayed.
/// </summary>
public new readonly Bindable<T> State = new Bindable<T>();
/// <summary>
/// Creates a new <see cref="StatefulMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type = MenuItemType.Standard)
: this(text, changeStateFunc, type, null)
{
}
/// <summary>
/// Creates a new <see cref="StatefulMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type, Action<T> action)
: base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o))
{
base.State.BindValueChanged(state =>
{
if (state.NewValue == null)
base.State.Value = default(T);
State.Value = (T)base.State.Value;
}, true);
State.BindValueChanged(state => base.State.Value = state.NewValue);
}
public sealed override IconUsage? GetIconForState(object state) => GetIconForState((T)state);
/// <summary>
/// Retrieves the icon to be displayed for a state.
/// </summary>
/// <param name="state">The state to retrieve the relevant icon for.</param>
/// <returns>The icon to be displayed for <paramref name="state"/>.</returns>
public abstract IconUsage? GetIconForState(T state);
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// An on/off state with an extra indeterminate state.
/// </summary>
public enum TernaryState
{
/// <summary>
/// The current state is false.
/// </summary>
False,
/// <summary>
/// The current state is a combination of <see cref="False"/> and <see cref="True"/>.
/// The state becomes <see cref="True"/> if the <see cref="TernaryStateMenuItem"/> is pressed.
/// </summary>
Indeterminate,
/// <summary>
/// The current state is true.
/// </summary>
True
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// An <see cref="OsuMenuItem"/> with three possible states.
/// </summary>
public class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
{
/// <summary>
/// Creates a new <see cref="TernaryStateMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: this(text, type, null)
{
}
/// <summary>
/// Creates a new <see cref="TernaryStateMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
public TernaryStateMenuItem(string text, MenuItemType type, Action<TernaryState> action)
: this(text, getNextState, type, action)
{
}
/// <summary>
/// Creates a new <see cref="TernaryStateMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="TernaryStateMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
protected TernaryStateMenuItem(string text, Func<TernaryState, TernaryState> changeStateFunc, MenuItemType type, Action<TernaryState> action)
: base(text, changeStateFunc, type, action)
{
}
public override IconUsage? GetIconForState(TernaryState state)
{
switch (state)
{
case TernaryState.Indeterminate:
return FontAwesome.Solid.DotCircle;
case TernaryState.True:
return FontAwesome.Solid.Check;
}
return null;
}
private static TernaryState getNextState(TernaryState state)
{
switch (state)
{
case TernaryState.False:
return TernaryState.True;
case TernaryState.Indeterminate:
return TernaryState.True;
case TernaryState.True:
return TernaryState.False;
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// An <see cref="OsuMenuItem"/> which displays an enabled or disabled state.
/// </summary>
public class ToggleMenuItem : StatefulMenuItem<bool>
{
/// <summary>
/// Creates a new <see cref="ToggleMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: this(text, type, null)
{
}
/// <summary>
/// Creates a new <see cref="ToggleMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="ToggleMenuItem"/> is pressed.</param>
public ToggleMenuItem(string text, MenuItemType type, Action<bool> action)
: base(text, value => !value, type, action)
{
}
public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null;
}
}

View File

@ -46,6 +46,7 @@ namespace osu.Game.Input
continue;
foreach (var insertable in group.Skip(count).Take(aimCount - count))
{
// insert any defaults which are missing.
usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding
{
@ -54,6 +55,7 @@ namespace osu.Game.Input
RulesetID = rulesetId,
Variant = variant
});
}
}
}
}

View File

@ -220,7 +220,7 @@ namespace osu.Game.Online.Chat
break;
}
var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault();
var channel = availableChannels.FirstOrDefault(c => c.Name == content || c.Name == $"#{content}");
if (channel == null)
{

View File

@ -75,8 +75,10 @@ namespace osu.Game.Online.Leaderboards
int i = 0;
foreach (var s in scrollFlow.Children)
{
using (s.BeginDelayedSequence(i++ * 50, true))
s.Show();
}
scrollContainer.ScrollTo(0f, false);
}, (showScoresCancellationSource = new CancellationTokenSource()).Token));
@ -342,13 +344,17 @@ namespace osu.Game.Online.Leaderboards
else
{
if (bottomY - fadeBottom > 0 && FadeBottom)
{
c.Colour = ColourInfo.GradientVertical(
Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)),
Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1)));
}
else if (FadeTop)
{
c.Colour = ColourInfo.GradientVertical(
Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)),
Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / LeaderboardScore.HEIGHT, 1)));
}
}
}
}

View File

@ -102,8 +102,6 @@ namespace osu.Game
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
private readonly List<VisibilityContainer> toolbarElements = new List<VisibilityContainer>();
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
public OsuGame(string[] args = null)
@ -134,17 +132,13 @@ namespace osu.Game
/// <summary>
/// Close all game-wide overlays.
/// </summary>
/// <param name="hideToolbarElements">Whether the toolbar (and accompanying controls) should also be hidden.</param>
public void CloseAllOverlays(bool hideToolbarElements = true)
/// <param name="hideToolbar">Whether the toolbar should also be hidden.</param>
public void CloseAllOverlays(bool hideToolbar = true)
{
foreach (var overlay in overlays)
overlay.Hide();
if (hideToolbarElements)
{
foreach (var overlay in toolbarElements)
overlay.Hide();
}
if (hideToolbar) Toolbar.Hide();
}
private DependencyContainer dependencies;
@ -393,6 +387,8 @@ namespace osu.Game
protected virtual Loader CreateLoader() => new Loader();
protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything);
#region Beatmap progression
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
@ -402,8 +398,10 @@ namespace osu.Game
nextBeatmap.Track.Completed += currentTrackCompleted;
using (var oldBeatmap = beatmap.OldValue)
{
if (oldBeatmap?.Track != null)
oldBeatmap.Track.Completed -= currentTrackCompleted;
}
nextBeatmap?.LoadBeatmapAsync();
}
@ -570,11 +568,7 @@ namespace osu.Game
CloseAllOverlays(false);
menuScreen?.MakeCurrent();
},
}, d =>
{
topMostOverlayContent.Add(d);
toolbarElements.Add(d);
});
}, topMostOverlayContent.Add);
loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true);
@ -613,11 +607,7 @@ namespace osu.Game
GetToolbarHeight = () => ToolbarOffset,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
}, d =>
{
rightFloatingOverlayContent.Add(d);
toolbarElements.Add(d);
}, true);
}, rightFloatingOverlayContent.Add, true);
loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true);
@ -627,8 +617,8 @@ namespace osu.Game
Add(externalLinkOpener = new ExternalLinkOpener());
// side overlays which cancel each other.
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
overlays.AddRange(singleDisplaySideOverlays);
foreach (var overlay in singleDisplaySideOverlays)
{
@ -642,7 +632,6 @@ namespace osu.Game
// eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time.
var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile };
overlays.AddRange(informationalOverlays);
foreach (var overlay in informationalOverlays)
{
@ -656,7 +645,6 @@ namespace osu.Game
// ensure only one of these overlays are open at once.
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay };
overlays.AddRange(singleDisplayOverlays);
foreach (var overlay in singleDisplayOverlays)
{
@ -757,6 +745,9 @@ namespace osu.Game
if (cache)
dependencies.Cache(d);
if (d is OverlayContainer overlay)
overlays.Add(overlay);
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
// with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile,
// we could avoid the need for scheduling altogether.

View File

@ -26,7 +26,6 @@ using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.IO;
@ -228,7 +227,7 @@ namespace osu.Game
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
};
base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer });
base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer));
KeyBindingStore.Register(globalBinding);
dependencies.Cache(globalBinding);
@ -238,6 +237,8 @@ namespace osu.Game
Add(previewTrackManager);
}
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
protected override void LoadComplete()
{
base.LoadComplete();
@ -292,8 +293,10 @@ namespace osu.Game
var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
foreach (var importer in fileImporters)
{
if (importer.HandledExtensions.Contains(extension))
await importer.Import(paths);
}
}
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();

View File

@ -130,6 +130,7 @@ namespace osu.Game.Overlays.Changelog
});
if (entry.GithubUser.UserId != null)
{
title.AddUserLink(new User
{
Username = entry.GithubUser.OsuUsername,
@ -139,18 +140,23 @@ namespace osu.Game.Overlays.Changelog
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else if (entry.GithubUser.GithubUrl != null)
{
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else
{
title.AddText(entry.GithubUser.DisplayName, t =>
{
t.Font = fontSmall;
t.Colour = entryColour;
});
}
ChangelogEntries.Add(titleContainer);

View File

@ -68,11 +68,13 @@ namespace osu.Game.Overlays.Changelog
}
if (build != null)
{
Children = new Drawable[]
{
new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild },
new Comments(build)
};
}
}
public class ChangelogBuildWithNavigation : ChangelogBuild

View File

@ -162,10 +162,12 @@ namespace osu.Game.Overlays.Comments
foreach (var c in response.Comments)
{
if (c.IsTopLevel)
{
page.Add(new DrawableComment(c)
{
ShowDeleted = { BindTarget = ShowDeleted }
});
}
}
LoadComponentAsync(page, loaded =>

View File

@ -149,8 +149,10 @@ namespace osu.Game.Overlays.Direct
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5));
}
else
{
foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty))
icons.Add(new DifficultyIcon(b));
}
return icons;
}

View File

@ -194,8 +194,10 @@ namespace osu.Game.Overlays.Mods
start = Mods.Length - 1;
for (int i = start; i < Mods.Length && i >= 0; i += direction)
{
if (SelectAt(i))
return;
}
Deselect();
}

View File

@ -112,6 +112,7 @@ namespace osu.Game.Overlays.Mods
if (selected == null) continue;
foreach (var type in modTypes)
{
if (type.IsInstanceOfType(selected))
{
if (immediate)
@ -119,6 +120,7 @@ namespace osu.Game.Overlays.Mods
else
Scheduler.AddDelayed(button.Deselect, delay += 50);
}
}
}
}
@ -158,7 +160,8 @@ namespace osu.Game.Overlays.Mods
},
ButtonsContainer = new FillFlowContainer<ModButtonEmpty>
{
AutoSizeAxes = Axes.Both,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Spacing = new Vector2(50f, 0f),

View File

@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Configuration.Tracking;
namespace osu.Game.Rulesets.Configuration
{
public interface IRulesetConfigManager : ITrackableConfigManager
public interface IRulesetConfigManager : ITrackableConfigManager, IDisposable
{
}
}

View File

@ -81,8 +81,10 @@ namespace osu.Game.Rulesets.Difficulty.Utils
yield return array[i];
if (Count == capacity)
{
for (int i = 0; i < marker; ++i)
yield return array[i];
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -104,6 +105,11 @@ namespace osu.Game.Rulesets.Edit
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
/// <summary>
/// The <see cref="MenuItem"/>s to be displayed in the context menu for this <see cref="SelectionBlueprint"/>.
/// </summary>
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
/// <summary>
/// The screen-space point that causes this <see cref="SelectionBlueprint"/> to be selected.
/// </summary>

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
protected virtual string SampleNamespace => null;
protected SkinnableSound Samples;
protected SkinnableSound Samples { get; private set; }
protected virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
@ -78,6 +78,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary>
public JudgementResult Result { get; private set; }
private BindableList<HitSampleInfo> samplesBindable;
private Bindable<double> startTimeBindable;
private Bindable<int> comboIndexBindable;
@ -108,20 +109,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
}
var samples = GetSamples().ToArray();
if (samples.Length > 0)
{
if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
foreach (var s in samples)
s.Namespace = SampleNamespace;
AddInternal(Samples = new SkinnableSound(samples));
}
loadSamples();
}
protected override void LoadComplete()
@ -139,10 +127,40 @@ namespace osu.Game.Rulesets.Objects.Drawables
comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true);
}
samplesBindable = HitObject.SamplesBindable.GetBoundCopy();
samplesBindable.ItemsAdded += _ => loadSamples();
samplesBindable.ItemsRemoved += _ => loadSamples();
updateState(ArmedState.Idle, true);
onDefaultsApplied();
}
private void loadSamples()
{
if (Samples != null)
{
RemoveInternal(Samples);
Samples = null;
}
var samples = GetSamples().ToArray();
if (samples.Length <= 0)
return;
if (HitObject.SampleControlPoint == null)
{
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
foreach (var s in samples)
s.Namespace = SampleNamespace;
AddInternal(Samples = new SkinnableSound(samples));
}
private void onDefaultsApplied() => apply(HitObject);
private void apply(HitObject hitObject)

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Objects
set => StartTimeBindable.Value = value;
}
private List<HitSampleInfo> samples;
public readonly BindableList<HitSampleInfo> SamplesBindable = new BindableList<HitSampleInfo>();
/// <summary>
/// The samples to be played when this hit object is hit.
@ -54,10 +54,14 @@ namespace osu.Game.Rulesets.Objects
/// and can be treated as the default samples for the hit object.
/// </para>
/// </summary>
public List<HitSampleInfo> Samples
public IList<HitSampleInfo> Samples
{
get => samples ?? (samples = new List<HitSampleInfo>());
set => samples = value;
get => SamplesBindable;
set
{
SamplesBindable.Clear();
SamplesBindable.AddRange(value);
}
}
[JsonIgnore]

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
List<List<HitSampleInfo>> nodeSamples)
List<IList<HitSampleInfo>> nodeSamples)
{
newCombo |= forceNewCombo;
comboOffset += extraComboOffset;

View File

@ -74,9 +74,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
string[] pointSplit = split[5].Split('|');
int pointCount = 1;
foreach (var t in pointSplit)
{
if (t.Length > 1)
pointCount++;
}
var points = new Vector2[pointCount];
@ -181,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
// Generate the final per-node samples
var nodeSamples = new List<List<HitSampleInfo>>(nodes);
var nodeSamples = new List<IList<HitSampleInfo>>(nodes);
for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
@ -279,7 +282,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
List<List<HitSampleInfo>> nodeSamples);
List<IList<HitSampleInfo>> nodeSamples);
/// <summary>
/// Creates a legacy Spinner-type hit object.

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
public double Distance => Path.Distance;
public List<List<HitSampleInfo>> NodeSamples { get; set; }
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
public int RepeatCount { get; set; }
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
List<List<HitSampleInfo>> nodeSamples)
List<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
List<List<HitSampleInfo>> nodeSamples)
List<IList<HitSampleInfo>> nodeSamples)
{
newCombo |= forceNewCombo;
comboOffset += extraComboOffset;

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
List<List<HitSampleInfo>> nodeSamples)
List<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{

View File

@ -185,8 +185,10 @@ namespace osu.Game.Rulesets.Objects
ReadOnlySpan<Vector2> cpSpan = ControlPoints.Slice(start, end - start);
foreach (Vector2 t in calculateSubpath(cpSpan))
{
if (calculatedPath.Count == 0 || calculatedPath.Last() != t)
calculatedPath.Add(t);
}
start = end;
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types
/// n-1: The last repeat.<br />
/// n: The last node.
/// </summary>
List<List<HitSampleInfo>> NodeSamples { get; }
List<IList<HitSampleInfo>> NodeSamples { get; }
}
public static class HasRepeatsExtensions

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets
// ensures any potential database operations are finalised before game destruction.
foreach (var c in configCache.Values)
(c as IDisposable)?.Dispose();
c?.Dispose();
}
}
}

View File

@ -69,8 +69,10 @@ namespace osu.Game.Rulesets
//add any other modes
foreach (var r in instances.Where(r => r.LegacyID == null))
{
if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null)
context.RulesetInfo.Add(r.RulesetInfo);
}
context.SaveChanges();

View File

@ -436,8 +436,10 @@ namespace osu.Game.Rulesets.UI
return h.HitWindows;
foreach (var n in h.NestedHitObjects)
{
if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return n.HitWindows;
}
}
return null;

View File

@ -130,9 +130,13 @@ namespace osu.Game.Rulesets.UI
base.Update();
if (beatmap != null)
{
foreach (var mod in mods)
{
if (mod is IUpdatableByPlayfield updatable)
updatable.Update(this);
}
}
}
/// <summary>

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
@ -17,10 +18,11 @@ using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Input;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class BlueprintContainer : CompositeDrawable
public class BlueprintContainer : CompositeDrawable, IKeyBindingHandler<PlatformAction>
{
public event Action<IEnumerable<HitObject>> SelectionChanged;
@ -96,11 +98,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnMouseDown(MouseDownEvent e)
{
beginClickSelection(e);
return true;
return e.Button == MouseButton.Left;
}
protected override bool OnClick(ClickEvent e)
{
if (e.Button == MouseButton.Right)
return false;
// Deselection should only occur if no selected blueprints are hovered
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
@ -112,6 +117,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnDoubleClick(DoubleClickEvent e)
{
if (e.Button == MouseButton.Right)
return false;
SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
if (clickedBlueprint == null)
@ -125,7 +133,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
// Special case for when a drag happened instead of a click
Schedule(() => endClickSelection());
return true;
return e.Button == MouseButton.Left;
}
protected override bool OnMouseMove(MouseMoveEvent e)
@ -141,6 +149,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button == MouseButton.Right)
return false;
if (!beginSelectionMovement())
{
dragBox.UpdateDrag(e);
@ -152,6 +163,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnDrag(DragEvent e)
{
if (e.Button == MouseButton.Right)
return false;
if (!moveCurrentSelection(e))
dragBox.UpdateDrag(e);
@ -160,6 +174,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnDragEnd(DragEndEvent e)
{
if (e.Button == MouseButton.Right)
return false;
if (!finishSelectionMovement())
{
dragBox.FadeOut(250, Easing.OutQuint);
@ -169,6 +186,37 @@ namespace osu.Game.Screens.Edit.Compose.Components
return true;
}
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
{
case Key.Escape:
if (!selectionHandler.SelectedBlueprints.Any())
return false;
deselectAll();
return true;
}
return false;
}
protected override bool OnKeyUp(KeyUpEvent e) => false;
public bool OnPressed(PlatformAction action)
{
switch (action.ActionType)
{
case PlatformActionType.SelectAll:
selectAll();
return true;
}
return false;
}
public bool OnReleased(PlatformAction action) => false;
protected override void Update()
{
base.Update();
@ -314,6 +362,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
}
/// <summary>
/// Selects all <see cref="SelectionBlueprint"/>s.
/// </summary>
private void selectAll()
{
selectionBlueprints.ToList().ForEach(m => m.Select());
selectionHandler.UpdateVisibility();
}
/// <summary>
/// Deselects all selected <see cref="SelectionBlueprint"/>s.
/// </summary>

View File

@ -7,11 +7,15 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.States;
using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@ -22,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary>
/// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
/// </summary>
public class SelectionHandler : CompositeDrawable, IKeyBindingHandler<PlatformAction>
public class SelectionHandler : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
{
public const float BORDER_RADIUS = 2;
@ -76,8 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
switch (action.ActionMethod)
{
case PlatformActionMethod.Delete:
foreach (var h in selectedBlueprints.ToList())
placementHandler.Delete(h.DrawableObject.HitObject);
deleteSelected();
return true;
}
@ -140,8 +143,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
UpdateVisibility();
}
private void deleteSelected()
{
foreach (var h in selectedBlueprints.ToList())
placementHandler.Delete(h.DrawableObject.HitObject);
}
#endregion
#region Outline Display
/// <summary>
/// Updates whether this <see cref="SelectionHandler"/> is visible.
/// </summary>
@ -176,5 +187,104 @@ namespace osu.Game.Screens.Edit.Compose.Components
outline.Size = bottomRight - topLeft;
outline.Position = topLeft;
}
#endregion
#region Sample Changes
/// <summary>
/// Adds a hit sample to all selected <see cref="HitObject"/>s.
/// </summary>
/// <param name="sampleName">The name of the hit sample.</param>
public void AddHitSample(string sampleName)
{
foreach (var h in SelectedHitObjects)
{
// Make sure there isn't already an existing sample
if (h.Samples.Any(s => s.Name == sampleName))
continue;
h.Samples.Add(new HitSampleInfo { Name = sampleName });
}
}
/// <summary>
/// Removes a hit sample from all selected <see cref="HitObject"/>s.
/// </summary>
/// <param name="sampleName">The name of the hit sample.</param>
public void RemoveHitSample(string sampleName)
{
foreach (var h in SelectedHitObjects)
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
}
#endregion
#region Context Menu
public virtual MenuItem[] ContextMenuItems
{
get
{
if (!selectedBlueprints.Any(b => b.IsHovered))
return Array.Empty<MenuItem>();
var items = new List<MenuItem>
{
new OsuMenuItem("Sound")
{
Items = new[]
{
createHitSampleMenuItem("Whistle", HitSampleInfo.HIT_WHISTLE),
createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP),
createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH)
}
},
new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected),
};
if (selectedBlueprints.Count == 1)
items.AddRange(selectedBlueprints[0].ContextMenuItems);
return items.ToArray();
}
}
private MenuItem createHitSampleMenuItem(string name, string sampleName)
{
return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState)
{
State = { Value = getHitSampleState() }
};
void setHitSampleState(TernaryState state)
{
switch (state)
{
case TernaryState.False:
RemoveHitSample(sampleName);
break;
case TernaryState.True:
AddHitSample(sampleName);
break;
}
}
TernaryState getHitSampleState()
{
int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName));
if (countExisting == 0)
return TernaryState.False;
if (countExisting < SelectedHitObjects.Count())
return TernaryState.Indeterminate;
return TernaryState.True;
}
}
#endregion
}
}

View File

@ -24,6 +24,7 @@ using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Setup;
@ -90,87 +91,91 @@ namespace osu.Game.Screens.Edit
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
InternalChildren = new[]
InternalChild = new OsuContextMenuContainer
{
new Container
RelativeSizeAxes = Axes.Both,
Children = new[]
{
Name = "Screen container",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 40, Bottom = 60 },
Child = screenContainer = new Container
new Container
{
Name = "Screen container",
RelativeSizeAxes = Axes.Both,
Masking = true
}
},
new Container
{
Name = "Top bar",
RelativeSizeAxes = Axes.X,
Height = 40,
Child = menuBar = new EditorMenuBar
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Items = new[]
{
new MenuItem("File")
{
Items = fileMenuItems
}
}
}
},
new Container
{
Name = "Bottom bar",
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 60,
Children = new Drawable[]
{
bottomBackground = new Box { RelativeSizeAxes = Axes.Both },
new Container
Padding = new MarginPadding { Top = 40, Bottom = 60 },
Child = screenContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = 5, Horizontal = 10 },
Child = new GridContainer
Masking = true
}
},
new Container
{
Name = "Top bar",
RelativeSizeAxes = Axes.X,
Height = 40,
Child = menuBar = new EditorMenuBar
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Items = new[]
{
new MenuItem("File")
{
Items = fileMenuItems
}
}
}
},
new Container
{
Name = "Bottom bar",
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 60,
Children = new Drawable[]
{
bottomBackground = new Box { RelativeSizeAxes = Axes.Both },
new Container
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
Padding = new MarginPadding { Vertical = 5, Horizontal = 10 },
Child = new GridContainer
{
new Dimension(GridSizeMode.Absolute, 220),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 220)
},
Content = new[]
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 10 },
Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
},
new SummaryTimeline
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10 },
Child = new PlaybackControl { RelativeSizeAxes = Axes.Both },
}
new Dimension(GridSizeMode.Absolute, 220),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 220)
},
}
},
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 10 },
Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
},
new SummaryTimeline
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10 },
Child = new PlaybackControl { RelativeSizeAxes = Axes.Both },
}
},
}
},
}
}
}
},
},
}
};
menuBar.Mode.ValueChanged += onModeChanged;

View File

@ -57,11 +57,13 @@ namespace osu.Game.Screens.Multi.Components
var beatmap = CurrentItem.Value?.Beatmap;
if (beatmap == null)
{
textFlow.AddText("No beatmap selected", s =>
{
s.Font = s.Font.With(size: TextSize);
s.Colour = colours.PinkLight;
});
}
else
{
textFlow.AddLink(new[]

View File

@ -100,9 +100,11 @@ namespace osu.Game.Screens.Play.HUD
if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered)
Alpha = 1;
else
{
Alpha = Interpolation.ValueAt(
MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200),
Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint);
}
}
private class Button : HoldToConfirmContainer, IKeyBindingHandler<GlobalAction>

View File

@ -181,8 +181,11 @@ namespace osu.Game.Screens.Play
this.FadeIn(500, Easing.OutExpo);
if (!IsHovered && !IsDragged)
{
using (BeginDelayedSequence(1000))
scheduledHide = Schedule(Hide);
}
break;
case Visibility.Hidden:

View File

@ -44,10 +44,14 @@ namespace osu.Game.Screens.Select.Carousel
criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode);
if (match)
{
foreach (var criteriaTerm in criteria.SearchTerms)
{
match &=
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
}
}
Filtered.Value = !match;
}

View File

@ -251,11 +251,13 @@ namespace osu.Game.Screens.Select
{
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && beatmaps.StableInstallationAvailable)
{
dialogOverlay.Push(new ImportFromStablePopup(() =>
{
Task.Run(beatmaps.ImportFromStableAsync).ContinueWith(_ => scores.ImportFromStableAsync(), TaskContinuationOptions.OnlyOnRanToCompletion);
Task.Run(skins.ImportFromStableAsync);
}));
}
});
}
}
@ -332,12 +334,14 @@ namespace osu.Game.Screens.Select
if (e.NewValue is DummyWorkingBeatmap) return;
if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false))
{
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch
if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value))
{
Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset;
Carousel.SelectBeatmap(e.NewValue.BeatmapInfo);
}
}
}
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.

View File

@ -35,9 +35,12 @@ namespace osu.Game.Skinning
: base(skin)
{
Stream stream = storage?.GetStream(filename);
if (stream != null)
{
using (LineBufferedReader reader = new LineBufferedReader(stream))
Configuration = new LegacySkinDecoder().Decode(reader);
}
else
Configuration = new DefaultSkinConfiguration();

Some files were not shown because too many files have changed in this diff Show More