mirror of
https://github.com/osukey/osukey.git
synced 2025-06-22 03:38:01 +09:00
Merge branch 'master' into new-chat-drawable-channel
This commit is contained in:
commit
1c63c27fdf
@ -2,12 +2,6 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-format": {
|
|
||||||
"version": "3.1.37601",
|
|
||||||
"commands": [
|
|
||||||
"dotnet-format"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"jetbrains.resharper.globaltools": {
|
"jetbrains.resharper.globaltools": {
|
||||||
"version": "2022.1.0-eap10",
|
"version": "2022.1.0-eap10",
|
||||||
"commands": [
|
"commands": [
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
# EditorConfig is awesome: http://editorconfig.org
|
# EditorConfig is awesome: http://editorconfig.org
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
[*.{csproj,props,targets}]
|
||||||
|
charset = utf-8-bom
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
@ -8,8 +16,19 @@ indent_style = space
|
|||||||
indent_size = 4
|
indent_size = 4
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
#license header
|
||||||
|
file_header_template = Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#Roslyn naming styles
|
#Roslyn naming styles
|
||||||
|
|
||||||
|
#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,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
|
#camelCase for private members
|
||||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||||
|
|
||||||
@ -172,24 +191,11 @@ csharp_style_prefer_index_operator = false:silent
|
|||||||
csharp_style_prefer_range_operator = false:silent
|
csharp_style_prefer_range_operator = false:silent
|
||||||
csharp_style_prefer_switch_expression = false:none
|
csharp_style_prefer_switch_expression = false:none
|
||||||
|
|
||||||
#Supressing roslyn built-in analyzers
|
[*.{yaml,yml}]
|
||||||
# Suppress: EC112
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
#Private method is unused
|
indent_size = 2
|
||||||
dotnet_diagnostic.IDE0051.severity = silent
|
trim_trailing_whitespace = true
|
||||||
#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
|
|
||||||
|
|
||||||
#Disable operator overloads requiring alternate named methods
|
|
||||||
dotnet_diagnostic.CA2225.severity = none
|
|
||||||
|
|
||||||
# Banned APIs
|
|
||||||
dotnet_diagnostic.RS0030.severity = error
|
|
||||||
|
|
||||||
dotnet_diagnostic.OLOC001.words_in_name = 5
|
dotnet_diagnostic.OLOC001.words_in_name = 5
|
||||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -33,6 +33,9 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/inspectcode
|
path: ${{ github.workspace }}/inspectcode
|
||||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json') }}-${{ hashFiles('.github/workflows/ci.yml' ) }}
|
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json') }}-${{ hashFiles('.github/workflows/ci.yml' ) }}
|
||||||
|
|
||||||
|
- name: Dotnet code style
|
||||||
|
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true
|
||||||
|
|
||||||
- name: CodeFileSanity
|
- name: CodeFileSanity
|
||||||
run: |
|
run: |
|
||||||
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
||||||
@ -46,10 +49,6 @@ jobs:
|
|||||||
done <<< $(dotnet codefilesanity)
|
done <<< $(dotnet codefilesanity)
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
|
|
||||||
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
|
|
||||||
# - name: .NET Format (Dry Run)
|
|
||||||
# run: dotnet format --dry-run --check
|
|
||||||
|
|
||||||
- name: InspectCode
|
- name: InspectCode
|
||||||
run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||||
|
|
||||||
|
55
.globalconfig
Normal file
55
.globalconfig
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
is_global = true
|
||||||
|
|
||||||
|
# .NET Code Style
|
||||||
|
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||||
|
|
||||||
|
# IDE0001: Simplify names
|
||||||
|
dotnet_diagnostic.IDE0001.severity = warning
|
||||||
|
|
||||||
|
# IDE0002: Simplify member access
|
||||||
|
dotnet_diagnostic.IDE0002.severity = warning
|
||||||
|
|
||||||
|
# IDE0003: Remove qualification
|
||||||
|
dotnet_diagnostic.IDE0003.severity = warning
|
||||||
|
|
||||||
|
# IDE0004: Remove unnecessary cast
|
||||||
|
dotnet_diagnostic.IDE0004.severity = warning
|
||||||
|
|
||||||
|
# IDE0005: Remove unnecessary imports
|
||||||
|
dotnet_diagnostic.IDE0005.severity = warning
|
||||||
|
|
||||||
|
# IDE0034: Simplify default literal
|
||||||
|
dotnet_diagnostic.IDE0034.severity = warning
|
||||||
|
|
||||||
|
# IDE0036: Sort modifiers
|
||||||
|
dotnet_diagnostic.IDE0036.severity = warning
|
||||||
|
|
||||||
|
# IDE0040: Add accessibility modifier
|
||||||
|
dotnet_diagnostic.IDE0040.severity = warning
|
||||||
|
|
||||||
|
# IDE0049: Use keyword for type name
|
||||||
|
dotnet_diagnostic.IDE0040.severity = warning
|
||||||
|
|
||||||
|
# IDE0055: Fix formatting
|
||||||
|
dotnet_diagnostic.IDE0055.severity = warning
|
||||||
|
|
||||||
|
# IDE0051: Private method is unused
|
||||||
|
dotnet_diagnostic.IDE0051.severity = silent
|
||||||
|
|
||||||
|
# IDE0052: Private member is unused
|
||||||
|
dotnet_diagnostic.IDE0052.severity = silent
|
||||||
|
|
||||||
|
# IDE0073: File header
|
||||||
|
dotnet_diagnostic.IDE0073.severity = warning
|
||||||
|
|
||||||
|
# IDE0130: Namespace mismatch with folder
|
||||||
|
dotnet_diagnostic.IDE0130.severity = warning
|
||||||
|
|
||||||
|
# IDE1006: Naming style
|
||||||
|
dotnet_diagnostic.IDE1006.severity = warning
|
||||||
|
|
||||||
|
#Disable operator overloads requiring alternate named methods
|
||||||
|
dotnet_diagnostic.CA2225.severity = none
|
||||||
|
|
||||||
|
# Banned APIs
|
||||||
|
dotnet_diagnostic.RS0030.severity = error
|
@ -1,4 +1,4 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
<OutputPath>bin\$(Configuration)</OutputPath>
|
<OutputPath>bin\$(Configuration)</OutputPath>
|
||||||
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.430.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.509.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -29,13 +29,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
|
|
||||||
protected CatchSelectionBlueprintTestScene()
|
protected CatchSelectionBlueprintTestScene()
|
||||||
{
|
{
|
||||||
EditorBeatmap = new EditorBeatmap(new CatchBeatmap
|
var catchBeatmap = new CatchBeatmap
|
||||||
{
|
{
|
||||||
BeatmapInfo =
|
BeatmapInfo =
|
||||||
{
|
{
|
||||||
Ruleset = new CatchRuleset().RulesetInfo,
|
Ruleset = new CatchRuleset().RulesetInfo,
|
||||||
}
|
}
|
||||||
}) { Difficulty = { CircleSize = 0 } };
|
};
|
||||||
|
EditorBeatmap = new EditorBeatmap(catchBeatmap) { Difficulty = { CircleSize = 0 } };
|
||||||
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
||||||
{
|
{
|
||||||
BeatLength = 100
|
BeatLength = 100
|
||||||
|
@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
|
|
||||||
AddMoveStep(end_time, 0);
|
AddMoveStep(end_time, 0);
|
||||||
AddClickStep(MouseButton.Left);
|
AddClickStep(MouseButton.Left);
|
||||||
|
|
||||||
AddMoveStep(start_time, 0);
|
AddMoveStep(start_time, 0);
|
||||||
|
AddAssert("duration is positive", () => ((BananaShower)CurrentBlueprint.HitObject).Duration > 0);
|
||||||
|
|
||||||
AddClickStep(MouseButton.Right);
|
AddClickStep(MouseButton.Right);
|
||||||
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||||
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -13,11 +14,21 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
private readonly TimeSpanOutline outline;
|
private readonly TimeSpanOutline outline;
|
||||||
|
|
||||||
|
private double placementStartTime;
|
||||||
|
private double placementEndTime;
|
||||||
|
|
||||||
public BananaShowerPlacementBlueprint()
|
public BananaShowerPlacementBlueprint()
|
||||||
{
|
{
|
||||||
InternalChild = outline = new TimeSpanOutline();
|
InternalChild = outline = new TimeSpanOutline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
BeginPlacement();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -38,13 +49,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
case PlacementState.Active:
|
case PlacementState.Active:
|
||||||
if (e.Button != MouseButton.Right) break;
|
if (e.Button != MouseButton.Right) break;
|
||||||
|
|
||||||
// If the duration is negative, swap the start and the end time to make the duration positive.
|
|
||||||
if (HitObject.Duration < 0)
|
|
||||||
{
|
|
||||||
HitObject.StartTime = HitObject.EndTime;
|
|
||||||
HitObject.Duration = -HitObject.Duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
EndPlacement(HitObject.Duration > 0);
|
EndPlacement(HitObject.Duration > 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -61,13 +65,16 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
{
|
{
|
||||||
case PlacementState.Waiting:
|
case PlacementState.Waiting:
|
||||||
HitObject.StartTime = time;
|
placementStartTime = placementEndTime = time;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Active:
|
case PlacementState.Active:
|
||||||
HitObject.EndTime = time;
|
placementEndTime = time;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HitObject.StartTime = Math.Min(placementStartTime, placementEndTime);
|
||||||
|
HitObject.EndTime = Math.Max(placementStartTime, placementEndTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
|
||||||
|
BeginPlacement();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
@ -107,6 +107,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
switch (e.Type)
|
switch (e.Type)
|
||||||
{
|
{
|
||||||
|
case SliderEventType.Tick:
|
||||||
|
AddNested(new SliderTick
|
||||||
|
{
|
||||||
|
SpanIndex = e.SpanIndex,
|
||||||
|
SpanStartTime = e.SpanStartTime,
|
||||||
|
StartTime = e.Time,
|
||||||
|
Position = Position + Path.PositionAt(e.PathProgress),
|
||||||
|
StackHeight = StackHeight,
|
||||||
|
Scale = Scale,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
case SliderEventType.Head:
|
case SliderEventType.Head:
|
||||||
AddNested(HeadCircle = new SliderHeadCircle
|
AddNested(HeadCircle = new SliderHeadCircle
|
||||||
{
|
{
|
||||||
|
@ -137,33 +137,137 @@ namespace osu.Game.Tests.Mods
|
|||||||
// incompatible pair.
|
// incompatible pair.
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() },
|
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||||
new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) }
|
new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) }
|
||||||
},
|
},
|
||||||
// incompatible pair with derived class.
|
// incompatible pair with derived class.
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
new Mod[] { new OsuModNightcore(), new OsuModHalfTime() },
|
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
|
||||||
new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) }
|
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
|
||||||
},
|
},
|
||||||
// system mod.
|
// system mod.
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
new Mod[] { new OsuModDoubleTime(), new OsuModTouchDevice() },
|
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||||
new[] { typeof(OsuModTouchDevice) }
|
new[] { typeof(OsuModTouchDevice) }
|
||||||
},
|
},
|
||||||
// multi mod.
|
// multi mod.
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModDaycore() },
|
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||||
new[] { typeof(MultiMod) }
|
new[] { typeof(MultiMod) }
|
||||||
},
|
},
|
||||||
|
// invalid multiplayer mod is valid for local.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||||
|
null
|
||||||
|
},
|
||||||
|
// invalid free mod is valid for local.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||||
|
null
|
||||||
|
},
|
||||||
// valid pair.
|
// valid pair.
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() },
|
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||||
null
|
null
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly object[] invalid_multiplayer_mod_test_scenarios =
|
||||||
|
{
|
||||||
|
// incompatible pair.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||||
|
new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) }
|
||||||
|
},
|
||||||
|
// incompatible pair with derived class.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
|
||||||
|
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
|
||||||
|
},
|
||||||
|
// system mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||||
|
new[] { typeof(OsuModTouchDevice) }
|
||||||
|
},
|
||||||
|
// multi mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||||
|
new[] { typeof(MultiMod) }
|
||||||
|
},
|
||||||
|
// invalid multiplayer mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||||
|
new[] { typeof(InvalidMultiplayerMod) }
|
||||||
|
},
|
||||||
|
// invalid free mod is valid for multiplayer global.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||||
|
null
|
||||||
|
},
|
||||||
|
// valid pair.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||||
|
null
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly object[] invalid_free_mod_test_scenarios =
|
||||||
|
{
|
||||||
|
// system mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||||
|
new[] { typeof(OsuModTouchDevice) }
|
||||||
|
},
|
||||||
|
// multi mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||||
|
new[] { typeof(MultiMod) }
|
||||||
|
},
|
||||||
|
// invalid multiplayer mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||||
|
new[] { typeof(InvalidMultiplayerMod) }
|
||||||
|
},
|
||||||
|
// invalid free mod.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||||
|
new[] { typeof(InvalidMultiplayerFreeMod) }
|
||||||
|
},
|
||||||
|
// incompatible pair is valid for free mods.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
// incompatible pair with derived class is valid for free mods.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModDeflate(), new OsuModSpinIn() },
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
// valid pair.
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||||
|
null
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(invalid_mod_test_scenarios))]
|
[TestCaseSource(nameof(invalid_mod_test_scenarios))]
|
||||||
@ -179,6 +283,32 @@ namespace osu.Game.Tests.Mods
|
|||||||
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))]
|
||||||
|
public void TestInvalidMultiplayerModScenarios(Mod[] inputMods, Type[] expectedInvalid)
|
||||||
|
{
|
||||||
|
bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid);
|
||||||
|
|
||||||
|
Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
Assert.IsNull(invalid);
|
||||||
|
else
|
||||||
|
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(invalid_free_mod_test_scenarios))]
|
||||||
|
public void TestInvalidFreeModScenarios(Mod[] inputMods, Type[] expectedInvalid)
|
||||||
|
{
|
||||||
|
bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid);
|
||||||
|
|
||||||
|
Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
Assert.IsNull(invalid);
|
||||||
|
else
|
||||||
|
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -187,6 +317,27 @@ namespace osu.Game.Tests.Mods
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class InvalidMultiplayerMod : Mod
|
||||||
|
{
|
||||||
|
public override string Name => string.Empty;
|
||||||
|
public override string Description => string.Empty;
|
||||||
|
public override string Acronym => string.Empty;
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override bool HasImplementation => true;
|
||||||
|
public override bool ValidForMultiplayer => false;
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InvalidMultiplayerFreeMod : Mod
|
||||||
|
{
|
||||||
|
public override string Name => string.Empty;
|
||||||
|
public override string Description => string.Empty;
|
||||||
|
public override string Acronym => string.Empty;
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override bool HasImplementation => true;
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
}
|
||||||
|
|
||||||
public interface IModCompatibilitySpecification
|
public interface IModCompatibilitySpecification
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -283,7 +282,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
|
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
|
||||||
AddStep("Set default user settings", () =>
|
AddStep("Set default user settings", () =>
|
||||||
{
|
{
|
||||||
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
|
SelectedMods.Value = new[] { new OsuModNoFail() };
|
||||||
songSelect.DimLevel.Value = 0.7f;
|
songSelect.DimLevel.Value = 0.7f;
|
||||||
songSelect.BlurLevel.Value = 0.4f;
|
songSelect.BlurLevel.Value = 0.4f;
|
||||||
});
|
});
|
||||||
|
@ -147,8 +147,7 @@ namespace osu.Game.Tests.Visual.Collections
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Width = 0.4f,
|
Width = 0.4f,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
|
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.OpenGL.Textures;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -131,7 +130,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Cached(typeof(ISkinSource))]
|
[Cached(typeof(ISkinSource))]
|
||||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
|
||||||
private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
|
private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
@ -1,29 +1,37 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
using osu.Game.Screens.OnlinePlay;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
public class TestSceneFreeModSelectScreen : MultiplayerTestScene
|
public class TestSceneFreeModSelectScreen : MultiplayerTestScene
|
||||||
{
|
{
|
||||||
|
private FreeModSelectScreen freeModSelectScreen;
|
||||||
|
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase osuGameBase)
|
||||||
|
{
|
||||||
|
availableMods.BindTo(osuGameBase.AvailableMods);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFreeModSelect()
|
public void TestFreeModSelect()
|
||||||
{
|
{
|
||||||
FreeModSelectScreen freeModSelectScreen = null;
|
createFreeModSelect();
|
||||||
|
|
||||||
AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen
|
|
||||||
{
|
|
||||||
State = { Value = Visibility.Visible }
|
|
||||||
});
|
|
||||||
AddUntilStep("all column content loaded",
|
|
||||||
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
|
|
||||||
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
|
||||||
|
|
||||||
AddUntilStep("all visible mods are playable",
|
AddUntilStep("all visible mods are playable",
|
||||||
() => this.ChildrenOfType<ModPanel>()
|
() => this.ChildrenOfType<ModPanel>()
|
||||||
@ -36,5 +44,62 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
freeModSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden;
|
freeModSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomisationNotAvailable()
|
||||||
|
{
|
||||||
|
createFreeModSelect();
|
||||||
|
|
||||||
|
AddStep("select difficulty adjust", () => freeModSelectScreen.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||||
|
AddWaitStep("wait some", 3);
|
||||||
|
AddAssert("customisation area not expanded", () => this.ChildrenOfType<ModSettingsArea>().Single().Height == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectDeselectAll()
|
||||||
|
{
|
||||||
|
createFreeModSelect();
|
||||||
|
|
||||||
|
AddStep("click select all button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
|
||||||
|
|
||||||
|
AddStep("click deselect all button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("all mods deselected", () => !freeModSelectScreen.SelectedMods.Value.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createFreeModSelect()
|
||||||
|
{
|
||||||
|
AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
});
|
||||||
|
AddUntilStep("all column content loaded",
|
||||||
|
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
|
||||||
|
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool assertAllAvailableModsSelected()
|
||||||
|
{
|
||||||
|
var allAvailableMods = availableMods.Value
|
||||||
|
.SelectMany(pair => pair.Value)
|
||||||
|
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var availableMod in allAvailableMods)
|
||||||
|
{
|
||||||
|
if (freeModSelectScreen.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -627,7 +627,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||||
|
|
||||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectScreen>().Single().State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
|
@ -132,7 +132,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void assertHasFreeModButton(Type type, bool hasButton = true)
|
private void assertHasFreeModButton(Type type, bool hasButton = true)
|
||||||
{
|
{
|
||||||
AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
|
AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
|
||||||
() => songSelect.ChildrenOfType<FreeModSelectOverlay>().Single().ChildrenOfType<ModButton>().All(b => b.Mod.GetType() != type));
|
() => this.ChildrenOfType<FreeModSelectScreen>()
|
||||||
|
.Single()
|
||||||
|
.ChildrenOfType<ModPanel>()
|
||||||
|
.Where(panel => !panel.Filtered.Value)
|
||||||
|
.All(b => b.Mod.GetType() != type));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
|
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
|
||||||
|
@ -168,8 +168,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
||||||
|
|
||||||
|
AddUntilStep("mod select contents loaded",
|
||||||
|
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||||
AddUntilStep("mod select contains only double time mod",
|
AddUntilStep("mod select contains only double time mod",
|
||||||
() => this.ChildrenOfType<UserModSelectOverlay>().SingleOrDefault()?.ChildrenOfType<ModButton>().SingleOrDefault()?.Mod is OsuModDoubleTime);
|
() => this.ChildrenOfType<UserModSelectScreen>()
|
||||||
|
.SingleOrDefault()?
|
||||||
|
.ChildrenOfType<ModPanel>()
|
||||||
|
.SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,23 @@ using osu.Game.Online.Rooms;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
public class TestSceneMultiplayerTeamResults : ScreenTestScene
|
public class TestSceneMultiplayerTeamResults : ScreenTestScene
|
||||||
{
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestScaling()
|
||||||
|
{
|
||||||
|
// scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason.
|
||||||
|
AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() =>
|
||||||
|
{
|
||||||
|
Stack.Scale = new Vector2(v);
|
||||||
|
Stack.Size = new Vector2(1f / v);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(7483253, 1048576)]
|
[TestCase(7483253, 1048576)]
|
||||||
[TestCase(1048576, 7483253)]
|
[TestCase(1048576, 7483253)]
|
||||||
[TestCase(1048576, 1048576)]
|
[TestCase(1048576, 1048576)]
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -253,12 +254,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
|
||||||
|
|
||||||
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
|
AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2(
|
||||||
AddUntilStep("Back button is hovered", () => Game.ChildrenOfType<BackButton>().First().Children.Any(c => c.IsHovered));
|
songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1,
|
||||||
|
songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2)));
|
||||||
|
AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
|
||||||
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
exitViaBackButtonAndConfirm();
|
exitViaBackButtonAndConfirm();
|
||||||
}
|
}
|
||||||
@ -503,6 +504,22 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddStep("test dispose doesn't crash", () => Game.Dispose());
|
AddStep("test dispose doesn't crash", () => Game.Dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRapidBackButtonExit()
|
||||||
|
{
|
||||||
|
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
|
||||||
|
|
||||||
|
AddStep("press escape twice rapidly", () =>
|
||||||
|
{
|
||||||
|
InputManager.Key(Key.Escape);
|
||||||
|
InputManager.Key(Key.Escape);
|
||||||
|
});
|
||||||
|
|
||||||
|
pushEscape();
|
||||||
|
|
||||||
|
AddAssert("exit dialog is shown", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog != null);
|
||||||
|
}
|
||||||
|
|
||||||
private Func<Player> playToResults()
|
private Func<Player> playToResults()
|
||||||
{
|
{
|
||||||
Player player = null;
|
Player player = null;
|
||||||
@ -551,7 +568,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
public class TestPlaySongSelect : PlaySongSelect
|
public class TestPlaySongSelect : PlaySongSelect
|
||||||
{
|
{
|
||||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
public ModSelectScreen ModSelectOverlay => ModSelect;
|
||||||
|
|
||||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
@ -18,22 +19,23 @@ using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
public class TestSceneSkinEditorSceneLibrary : OsuGameTestScene
|
public class TestSceneSkinEditorNavigation : OsuGameTestScene
|
||||||
{
|
{
|
||||||
private SkinEditor skinEditor;
|
private TestPlaySongSelect songSelect;
|
||||||
|
private SkinEditor skinEditor => Game.ChildrenOfType<SkinEditor>().FirstOrDefault();
|
||||||
|
|
||||||
public override void SetUpSteps()
|
private void advanceToSongSelect()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
|
||||||
|
|
||||||
Screens.Select.SongSelect songSelect = null;
|
|
||||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||||
|
|
||||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openSkinEditor()
|
||||||
|
{
|
||||||
AddStep("open skin editor", () =>
|
AddStep("open skin editor", () =>
|
||||||
{
|
{
|
||||||
InputManager.PressKey(Key.ControlLeft);
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
@ -42,13 +44,15 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
InputManager.ReleaseKey(Key.ControlLeft);
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
AddUntilStep("skin editor loaded", () => skinEditor != null);
|
||||||
AddUntilStep("get skin editor", () => (skinEditor = Game.ChildrenOfType<SkinEditor>().FirstOrDefault()) != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEditComponentDuringGameplay()
|
public void TestEditComponentDuringGameplay()
|
||||||
{
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
openSkinEditor();
|
||||||
|
|
||||||
switchToGameplayScene();
|
switchToGameplayScene();
|
||||||
|
|
||||||
BarHitErrorMeter hitErrorMeter = null;
|
BarHitErrorMeter hitErrorMeter = null;
|
||||||
@ -85,6 +89,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay()
|
public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay()
|
||||||
{
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
openSkinEditor();
|
||||||
AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||||
|
|
||||||
switchToGameplayScene();
|
switchToGameplayScene();
|
||||||
@ -95,6 +101,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAutoplayIncompatibleModsRemovedOnEnteringGameplay()
|
public void TestAutoplayIncompatibleModsRemovedOnEnteringGameplay()
|
||||||
{
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
openSkinEditor();
|
||||||
AddStep("select no fail and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModSpunOut() });
|
AddStep("select no fail and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModSpunOut() });
|
||||||
|
|
||||||
switchToGameplayScene();
|
switchToGameplayScene();
|
||||||
@ -105,6 +113,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDuplicateAutoplayModRemovedOnEnteringGameplay()
|
public void TestDuplicateAutoplayModRemovedOnEnteringGameplay()
|
||||||
{
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
openSkinEditor();
|
||||||
AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() });
|
AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() });
|
||||||
|
|
||||||
switchToGameplayScene();
|
switchToGameplayScene();
|
||||||
@ -115,6 +125,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCinemaModRemovedOnEnteringGameplay()
|
public void TestCinemaModRemovedOnEnteringGameplay()
|
||||||
{
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
openSkinEditor();
|
||||||
AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
||||||
|
|
||||||
switchToGameplayScene();
|
switchToGameplayScene();
|
||||||
@ -122,6 +134,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModOverlayClosesOnOpeningSkinEditor()
|
||||||
|
{
|
||||||
|
advanceToSongSelect();
|
||||||
|
AddStep("open mod overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
|
|
||||||
|
openSkinEditor();
|
||||||
|
AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
private void switchToGameplayScene()
|
private void switchToGameplayScene()
|
||||||
{
|
{
|
||||||
AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay").TriggerClick());
|
AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay").TriggerClick());
|
@ -56,6 +56,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScaling()
|
||||||
|
{
|
||||||
|
// scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason.
|
||||||
|
AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() =>
|
||||||
|
{
|
||||||
|
Content.Scale = new Vector2(v);
|
||||||
|
Content.Size = new Vector2(1f / v);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestResultsWithoutPlayer()
|
public void TestResultsWithoutPlayer()
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -18,6 +19,7 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
@ -918,6 +920,19 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModOverlayToggling()
|
||||||
|
{
|
||||||
|
changeRuleset(0);
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1));
|
||||||
|
AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1));
|
||||||
|
AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForInitialSelection()
|
private void waitForInitialSelection()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
||||||
@ -993,6 +1008,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
||||||
public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
|
public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
|
||||||
public new BeatmapCarousel Carousel => base.Carousel;
|
public new BeatmapCarousel Carousel => base.Carousel;
|
||||||
|
public new ModSelectScreen ModSelect => base.ModSelect;
|
||||||
|
|
||||||
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
||||||
|
|
||||||
|
@ -415,6 +415,72 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value);
|
AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeselectAllViaButton()
|
||||||
|
{
|
||||||
|
createScreen();
|
||||||
|
changeRuleset(0);
|
||||||
|
|
||||||
|
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
|
||||||
|
AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
|
||||||
|
|
||||||
|
AddStep("click deselect all button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCloseViaBackButton()
|
||||||
|
{
|
||||||
|
createScreen();
|
||||||
|
changeRuleset(0);
|
||||||
|
|
||||||
|
AddStep("select difficulty adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||||
|
assertCustomisationToggleState(disabled: false, active: true);
|
||||||
|
AddAssert("back button disabled", () => !this.ChildrenOfType<ShearedButton>().First().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("dismiss customisation area", () => InputManager.Key(Key.Escape));
|
||||||
|
AddStep("click back button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestColumnHiding()
|
||||||
|
{
|
||||||
|
AddStep("create screen", () => Child = modSelectScreen = new UserModSelectScreen
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
State = { Value = Visibility.Visible },
|
||||||
|
SelectedMods = { BindTarget = SelectedMods },
|
||||||
|
IsValidMod = mod => mod.Type == ModType.DifficultyIncrease || mod.Type == ModType.Conversion
|
||||||
|
});
|
||||||
|
waitForColumnLoad();
|
||||||
|
changeRuleset(0);
|
||||||
|
|
||||||
|
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||||
|
|
||||||
|
AddStep("unset filter", () => modSelectScreen.IsValidMod = _ => true);
|
||||||
|
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||||
|
|
||||||
|
AddStep("filter out everything", () => modSelectScreen.IsValidMod = _ => false);
|
||||||
|
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
||||||
|
|
||||||
|
AddStep("hide", () => modSelectScreen.Hide());
|
||||||
|
AddStep("set filter for 3 columns", () => modSelectScreen.IsValidMod = mod => mod.Type == ModType.DifficultyReduction
|
||||||
|
|| mod.Type == ModType.Automation
|
||||||
|
|| mod.Type == ModType.Conversion);
|
||||||
|
|
||||||
|
AddStep("show", () => modSelectScreen.Show());
|
||||||
|
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
||||||
() => modSelectScreen.ChildrenOfType<ModColumn>().Any() && modSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
() => modSelectScreen.ChildrenOfType<ModColumn>().Any() && modSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
|
||||||
|
@ -66,7 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
public class TestShearedOverlayContainer : ShearedOverlayContainer
|
public class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||||
{
|
{
|
||||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
|
public TestShearedOverlayContainer()
|
||||||
|
: base(OverlayColourScheme.Green)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows a component to disable sample playback dynamically as required.
|
/// Allows a component to disable sample playback dynamically as required.
|
||||||
/// Handled by <see cref="PausableSkinnableSound"/>.
|
/// Automatically handled by <see cref="PausableSkinnableSound"/>.
|
||||||
|
/// May also be manually handled locally to particular components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Cached]
|
||||||
public interface ISamplePlaybackDisabler
|
public interface ISamplePlaybackDisabler
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
@ -153,7 +153,17 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Task.Run(() => cacheDownloadRequest.PerformAsync());
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await cacheDownloadRequest.PerformAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway.
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo)
|
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo)
|
||||||
|
@ -431,9 +431,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
OmitFirstBarLine = omitFirstBarSignature,
|
OmitFirstBarLine = omitFirstBarSignature,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0;
|
int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
|
||||||
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
|
|
||||||
if (!isOsuRuleset)
|
// osu!taiko and osu!mania use effect points rather than difficulty points for scroll speed adjustments.
|
||||||
|
if (onlineRulesetID == 1 || onlineRulesetID == 3)
|
||||||
effectPoint.ScrollSpeed = speedMultiplier;
|
effectPoint.ScrollSpeed = speedMultiplier;
|
||||||
|
|
||||||
addControlPoint(time, effectPoint, timingChange);
|
addControlPoint(time, effectPoint, timingChange);
|
||||||
|
@ -183,15 +183,15 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
SampleControlPoint lastRelevantSamplePoint = null;
|
SampleControlPoint lastRelevantSamplePoint = null;
|
||||||
DifficultyControlPoint lastRelevantDifficultyPoint = null;
|
DifficultyControlPoint lastRelevantDifficultyPoint = null;
|
||||||
|
|
||||||
bool isOsuRuleset = onlineRulesetID == 0;
|
// In osu!taiko and osu!mania, a scroll speed is stored as "slider velocity" in legacy formats.
|
||||||
|
// In that case, a scrolling speed change is a global effect and per-hit object difficulty control points are ignored.
|
||||||
|
bool scrollSpeedEncodedAsSliderVelocity = onlineRulesetID == 1 || onlineRulesetID == 3;
|
||||||
|
|
||||||
// iterate over hitobjects and pull out all required sample and difficulty changes
|
// iterate over hitobjects and pull out all required sample and difficulty changes
|
||||||
extractDifficultyControlPoints(beatmap.HitObjects);
|
extractDifficultyControlPoints(beatmap.HitObjects);
|
||||||
extractSampleControlPoints(beatmap.HitObjects);
|
extractSampleControlPoints(beatmap.HitObjects);
|
||||||
|
|
||||||
// handle scroll speed, which is stored as "slider velocity" in legacy formats.
|
if (scrollSpeedEncodedAsSliderVelocity)
|
||||||
// this is relevant for scrolling ruleset beatmaps.
|
|
||||||
if (!isOsuRuleset)
|
|
||||||
{
|
{
|
||||||
foreach (var point in legacyControlPoints.EffectPoints)
|
foreach (var point in legacyControlPoints.EffectPoints)
|
||||||
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
||||||
@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
||||||
{
|
{
|
||||||
if (!isOsuRuleset)
|
if (scrollSpeedEncodedAsSliderVelocity)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var hitObject in hitObjects)
|
foreach (var hitObject in hitObjects)
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
protected virtual bool DimMainContent => true;
|
protected virtual bool DimMainContent => true;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuGame game { get; set; }
|
private IOverlayManager overlayManager { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private PreviewTrackManager previewTrackManager { get; set; }
|
private PreviewTrackManager previewTrackManager { get; set; }
|
||||||
@ -50,8 +50,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (overlayManager != null)
|
||||||
OverlayActivationMode.BindTo(game.OverlayActivationMode);
|
OverlayActivationMode.BindTo(overlayManager.OverlayActivationMode);
|
||||||
|
|
||||||
OverlayActivationMode.BindValueChanged(mode =>
|
OverlayActivationMode.BindValueChanged(mode =>
|
||||||
{
|
{
|
||||||
@ -127,14 +127,14 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (didChange)
|
if (didChange)
|
||||||
samplePopIn?.Play();
|
samplePopIn?.Play();
|
||||||
|
|
||||||
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Visibility.Hidden:
|
case Visibility.Hidden:
|
||||||
if (didChange)
|
if (didChange)
|
||||||
samplePopOut?.Play();
|
samplePopOut?.Play();
|
||||||
|
|
||||||
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
game?.RemoveBlockingOverlay(this);
|
overlayManager?.HideBlockingOverlay(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Importing => new TranslatableString(getKey(@"importing"), @"Importing...");
|
public static LocalisableString Importing => new TranslatableString(getKey(@"importing"), @"Importing...");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Deselect All"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DeselectAll => new TranslatableString(getKey(@"deselect_all"), @"Deselect All");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Select All"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SelectAll => new TranslatableString(getKey(@"select_all"), @"Select All");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
Normal file
19
osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class DifficultyMultiplierDisplayStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.DifficultyMultiplierDisplay";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Difficulty Multiplier"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DifficultyMultiplier => new TranslatableString(getKey(@"difficulty_multiplier"), @"Difficulty Multiplier");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game/Localisation/ModSelectScreenStrings.cs
Normal file
29
osu.Game/Localisation/ModSelectScreenStrings.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class ModSelectScreenStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.ModSelectScreen";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Mod Select"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ModSelectTitle => new TranslatableString(getKey(@"mod_select_title"), @"Mod Select");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ModSelectDescription => new TranslatableString(getKey(@"mod_select_description"), @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Mod Customisation"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using System;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using System;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game
|
|||||||
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
|
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
|
||||||
/// for initial components that are generally retrieved via DI.
|
/// for initial components that are generally retrieved via DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner
|
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
||||||
@ -171,6 +171,7 @@ namespace osu.Game
|
|||||||
private readonly string[] args;
|
private readonly string[] args;
|
||||||
|
|
||||||
private readonly List<OsuFocusedOverlayContainer> focusedOverlays = new List<OsuFocusedOverlayContainer>();
|
private readonly List<OsuFocusedOverlayContainer> focusedOverlays = new List<OsuFocusedOverlayContainer>();
|
||||||
|
private readonly List<OverlayContainer> externalOverlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
|
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
@ -183,22 +184,58 @@ namespace osu.Game
|
|||||||
SentryLogger = new SentryLogger(this);
|
SentryLogger = new SentryLogger(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IOverlayManager
|
||||||
|
|
||||||
|
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode => OverlayActivationMode;
|
||||||
|
|
||||||
private void updateBlockingOverlayFade() =>
|
private void updateBlockingOverlayFade() =>
|
||||||
ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
|
ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
|
||||||
|
|
||||||
public void AddBlockingOverlay(OverlayContainer overlay)
|
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||||
|
{
|
||||||
|
if (overlayContainer.Parent != null)
|
||||||
|
throw new ArgumentException($@"Overlays registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} should not be added to the scene graph.");
|
||||||
|
|
||||||
|
if (externalOverlays.Contains(overlayContainer))
|
||||||
|
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
|
||||||
|
|
||||||
|
externalOverlays.Add(overlayContainer);
|
||||||
|
overlayContent.Add(overlayContainer);
|
||||||
|
|
||||||
|
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
||||||
|
focusedOverlays.Add(focusedOverlayContainer);
|
||||||
|
|
||||||
|
return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
|
||||||
{
|
{
|
||||||
if (!visibleBlockingOverlays.Contains(overlay))
|
if (!visibleBlockingOverlays.Contains(overlay))
|
||||||
visibleBlockingOverlays.Add(overlay);
|
visibleBlockingOverlays.Add(overlay);
|
||||||
updateBlockingOverlayFade();
|
updateBlockingOverlayFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
|
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
|
||||||
{
|
{
|
||||||
visibleBlockingOverlays.Remove(overlay);
|
visibleBlockingOverlays.Remove(overlay);
|
||||||
updateBlockingOverlayFade();
|
updateBlockingOverlayFade();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself.
|
||||||
|
/// </summary>
|
||||||
|
private void unregisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||||
|
{
|
||||||
|
externalOverlays.Remove(overlayContainer);
|
||||||
|
|
||||||
|
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
||||||
|
focusedOverlays.Remove(focusedOverlayContainer);
|
||||||
|
|
||||||
|
overlayContainer.Expire();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close all game-wide overlays.
|
/// Close all game-wide overlays.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1153,6 +1190,7 @@ namespace osu.Game
|
|||||||
horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
|
horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
|
||||||
|
|
||||||
ScreenOffsetContainer.X = horizontalOffset;
|
ScreenOffsetContainer.X = horizontalOffset;
|
||||||
|
overlayContent.X = horizontalOffset * 1.2f;
|
||||||
|
|
||||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
||||||
|
|
||||||
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
||||||
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString);
|
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default;
|
||||||
|
|
||||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
||||||
modsColumn.Mods = value.Mods;
|
modsColumn.Mods = value.Mods;
|
||||||
|
@ -100,10 +100,6 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always want dialogs to show their appear animation, so we request they start hidden.
|
|
||||||
// Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete().
|
|
||||||
protected override bool StartHidden => true;
|
|
||||||
|
|
||||||
protected PopupDialog()
|
protected PopupDialog()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -272,7 +268,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
{
|
{
|
||||||
if (!actionInvoked && content.IsPresent)
|
if (!actionInvoked)
|
||||||
// In the case a user did not choose an action before a hide was triggered, press the last button.
|
// In the case a user did not choose an action before a hide was triggered, press the last button.
|
||||||
// This is presumed to always be a sane default "cancel" action.
|
// This is presumed to always be a sane default "cancel" action.
|
||||||
buttonsContainer.Last().TriggerClick();
|
buttonsContainer.Last().TriggerClick();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -19,6 +20,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -131,6 +133,10 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
[Cached(typeof(IBindable<WorkingBeatmap>))]
|
[Cached(typeof(IBindable<WorkingBeatmap>))]
|
||||||
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } = new Bindable<WorkingBeatmap>();
|
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||||
|
protected Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
public override bool HandlePositionalInput => false;
|
public override bool HandlePositionalInput => false;
|
||||||
public override bool HandleNonPositionalInput => false;
|
public override bool HandleNonPositionalInput => false;
|
||||||
public override bool PropagatePositionalInputSubTree => false;
|
public override bool PropagatePositionalInputSubTree => false;
|
||||||
|
@ -31,8 +31,6 @@ namespace osu.Game.Overlays
|
|||||||
[Cached]
|
[Cached]
|
||||||
public class FirstRunSetupOverlay : ShearedOverlayContainer
|
public class FirstRunSetupOverlay : ShearedOverlayContainer
|
||||||
{
|
{
|
||||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Purple;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IPerformFromScreenRunner performer { get; set; } = null!;
|
private IPerformFromScreenRunner performer { get; set; } = null!;
|
||||||
|
|
||||||
@ -70,6 +68,11 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Container content = null!;
|
private Container content = null!;
|
||||||
|
|
||||||
|
public FirstRunSetupOverlay()
|
||||||
|
: base(OverlayColourScheme.Purple)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
|
44
osu.Game/Overlays/IOverlayManager.cs
Normal file
44
osu.Game/Overlays/IOverlayManager.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
internal interface IOverlayManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
|
||||||
|
/// </summary>
|
||||||
|
IBindable<OverlayActivation> OverlayActivationMode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself for later use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The goal of this method is to allow child screens, like <see cref="SongSelect"/> to register their own full-screen blocking overlays
|
||||||
|
/// with background dim.
|
||||||
|
/// In those cases, for the dim to work correctly, the overlays need to be added at a game level directly, rather as children of the screens.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IDisposable"/> that should be disposed of when the <paramref name="overlayContainer"/> should be unregistered.
|
||||||
|
/// Disposing of this <see cref="IDisposable"/> will automatically expire the <paramref name="overlayContainer"/>.
|
||||||
|
/// </returns>
|
||||||
|
IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should be called when <paramref name="overlay"/> has been shown and should begin blocking background input.
|
||||||
|
/// </summary>
|
||||||
|
void ShowBlockingOverlay(OverlayContainer overlay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should be called when a blocking <paramref name="overlay"/> has been hidden and should stop blocking background input.
|
||||||
|
/// </summary>
|
||||||
|
void HideBlockingOverlay(OverlayContainer overlay);
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
@ -99,7 +100,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Margin = new MarginPadding { Horizontal = 18 },
|
Margin = new MarginPadding { Horizontal = 18 },
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
Text = "Difficulty Multiplier",
|
Text = DifficultyMultiplierDisplayStrings.DifficultyMultiplier,
|
||||||
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -22,6 +23,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -39,7 +41,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private Func<Mod, bool>? filter;
|
private Func<Mod, bool>? filter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Function determining whether each mod in the column should be displayed.
|
/// A function determining whether each mod in the column should be displayed.
|
||||||
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
||||||
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -49,12 +51,22 @@ namespace osu.Game.Overlays.Mods
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
filter = value;
|
filter = value;
|
||||||
updateFilter();
|
updateState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this column should accept user input.
|
||||||
|
/// </summary>
|
||||||
public Bindable<bool> Active = new BindableBool(true);
|
public Bindable<bool> Active = new BindableBool(true);
|
||||||
|
|
||||||
|
private readonly Bindable<bool> allFiltered = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if all of the panels in this column have been filtered out by the current <see cref="Filter"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> AllFiltered => allFiltered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of mods marked as selected in this column.
|
/// List of mods marked as selected in this column.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -186,7 +198,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new NestedVerticalScrollContainer
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ClampExtension = 100,
|
ClampExtension = 100,
|
||||||
@ -220,7 +232,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Scale = new Vector2(0.8f),
|
Scale = new Vector2(0.8f),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
LabelText = "Enable All",
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||||
});
|
});
|
||||||
panelFlow.Padding = new MarginPadding
|
panelFlow.Padding = new MarginPadding
|
||||||
@ -249,9 +260,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours)
|
private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
availableMods.BindTo(game.AvailableMods);
|
availableMods.BindTo(game.AvailableMods);
|
||||||
// this `BindValueChanged` callback is intentionally here, to ensure that local available mods are constructed as early as possible.
|
updateLocalAvailableMods(asyncLoadContent: false);
|
||||||
// this is needed to make sure no external changes to mods are dropped while mod panels are asynchronously loading.
|
availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true));
|
||||||
availableMods.BindValueChanged(_ => updateLocalAvailableMods(), true);
|
|
||||||
|
|
||||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
||||||
|
|
||||||
@ -265,7 +275,20 @@ namespace osu.Game.Overlays.Mods
|
|||||||
contentBackground.Colour = colourProvider.Background4;
|
contentBackground.Colour = colourProvider.Background4;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLocalAvailableMods()
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateToggleAllText()
|
||||||
|
{
|
||||||
|
Debug.Assert(toggleAllCheckbox != null);
|
||||||
|
toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLocalAvailableMods(bool asyncLoadContent)
|
||||||
{
|
{
|
||||||
var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty<Mod>())
|
var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty<Mod>())
|
||||||
.Select(m => m.DeepClone())
|
.Select(m => m.DeepClone())
|
||||||
@ -275,32 +298,24 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
localAvailableMods = newMods;
|
localAvailableMods = newMods;
|
||||||
Scheduler.AddOnce(loadPanels);
|
|
||||||
|
if (asyncLoadContent)
|
||||||
|
asyncLoadPanels();
|
||||||
|
else
|
||||||
|
onPanelsLoaded(createPanels());
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource? cancellationTokenSource;
|
private CancellationTokenSource? cancellationTokenSource;
|
||||||
|
|
||||||
private void loadPanels()
|
private void asyncLoadPanels()
|
||||||
{
|
{
|
||||||
cancellationTokenSource?.Cancel();
|
cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
var panels = createPanels();
|
||||||
|
|
||||||
Task? loadTask;
|
Task? loadTask;
|
||||||
|
|
||||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
{
|
|
||||||
panelFlow.ChildrenEnumerable = loaded;
|
|
||||||
|
|
||||||
updateActiveState();
|
|
||||||
updateToggleAllState();
|
|
||||||
updateFilter();
|
|
||||||
|
|
||||||
foreach (var panel in panelFlow)
|
|
||||||
{
|
|
||||||
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
|
|
||||||
}
|
|
||||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
|
||||||
loadTask.ContinueWith(_ =>
|
loadTask.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
if (loadTask == latestLoadTask)
|
if (loadTask == latestLoadTask)
|
||||||
@ -308,10 +323,39 @@ namespace osu.Game.Overlays.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateActiveState()
|
private IEnumerable<ModPanel> createPanels()
|
||||||
|
{
|
||||||
|
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
||||||
|
return panels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPanelsLoaded(IEnumerable<ModPanel> loaded)
|
||||||
|
{
|
||||||
|
panelFlow.ChildrenEnumerable = loaded;
|
||||||
|
|
||||||
|
updateState();
|
||||||
|
|
||||||
|
foreach (var panel in panelFlow)
|
||||||
|
{
|
||||||
|
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
{
|
{
|
||||||
foreach (var panel in panelFlow)
|
foreach (var panel in panelFlow)
|
||||||
|
{
|
||||||
panel.Active.Value = SelectedMods.Contains(panel.Mod);
|
panel.Active.Value = SelectedMods.Contains(panel.Mod);
|
||||||
|
panel.ApplyFilter(Filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value);
|
||||||
|
|
||||||
|
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
||||||
|
{
|
||||||
|
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
||||||
|
toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -323,14 +367,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private void panelStateChanged(ModPanel panel)
|
private void panelStateChanged(ModPanel panel)
|
||||||
{
|
{
|
||||||
updateToggleAllState();
|
if (externalSelectionUpdateInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
var newSelectedMods = panel.Active.Value
|
var newSelectedMods = panel.Active.Value
|
||||||
? SelectedMods.Append(panel.Mod)
|
? SelectedMods.Append(panel.Mod)
|
||||||
: SelectedMods.Except(panel.Mod.Yield());
|
: SelectedMods.Except(panel.Mod.Yield());
|
||||||
|
|
||||||
SelectedMods = newSelectedMods.ToArray();
|
SelectedMods = newSelectedMods.ToArray();
|
||||||
if (!externalSelectionUpdateInProgress)
|
updateState();
|
||||||
SelectionChangedByUser?.Invoke();
|
SelectionChangedByUser?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,7 +409,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectedMods = newSelection;
|
SelectedMods = newSelection;
|
||||||
updateActiveState();
|
updateState();
|
||||||
|
|
||||||
externalSelectionUpdateInProgress = false;
|
externalSelectionUpdateInProgress = false;
|
||||||
}
|
}
|
||||||
@ -378,7 +423,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>();
|
private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>();
|
||||||
|
|
||||||
protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
|
internal bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -403,15 +448,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateToggleAllState()
|
|
||||||
{
|
|
||||||
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
|
||||||
{
|
|
||||||
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
|
||||||
toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects all mods.
|
/// Selects all mods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -507,18 +543,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Filtering support
|
|
||||||
|
|
||||||
private void updateFilter()
|
|
||||||
{
|
|
||||||
foreach (var modPanel in panelFlow)
|
|
||||||
modPanel.ApplyFilter(Filter);
|
|
||||||
|
|
||||||
updateToggleAllState();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Keyboard selection support
|
#region Keyboard selection support
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -12,6 +14,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -21,8 +24,6 @@ using osu.Game.Rulesets.UI;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public class ModPanel : OsuClickableContainer
|
public class ModPanel : OsuClickableContainer
|
||||||
@ -50,6 +51,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private Colour4 activeColour;
|
private Colour4 activeColour;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
||||||
private Sample? sampleOff;
|
private Sample? sampleOff;
|
||||||
private Sample? sampleOn;
|
private Sample? sampleOn;
|
||||||
|
|
||||||
@ -139,13 +141,16 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Action = Active.Toggle;
|
Action = Active.Toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, OsuColour colours)
|
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||||
{
|
{
|
||||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||||
|
|
||||||
activeColour = colours.ForModType(Mod.Type);
|
activeColour = colours.ForModType(Mod.Type);
|
||||||
|
|
||||||
|
if (samplePlaybackDisabler != null)
|
||||||
|
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
@ -166,6 +171,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private void playStateChangeSamples()
|
private void playStateChangeSamples()
|
||||||
{
|
{
|
||||||
|
if (samplePlaybackDisabled.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Active.Value)
|
if (Active.Value)
|
||||||
sampleOn?.Play();
|
sampleOn?.Play();
|
||||||
else
|
else
|
||||||
|
@ -13,27 +13,35 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModSelectScreen : ShearedOverlayContainer
|
public abstract class ModSelectScreen : ShearedOverlayContainer, ISamplePlaybackDisabler
|
||||||
{
|
{
|
||||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
|
protected const int BUTTON_WIDTH = 200;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
private Func<Mod, bool> isValidMod = m => true;
|
private Func<Mod, bool> isValidMod = m => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function determining whether each mod in the column should be displayed.
|
||||||
|
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
||||||
|
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
||||||
|
/// </summary>
|
||||||
public Func<Mod, bool> IsValidMod
|
public Func<Mod, bool> IsValidMod
|
||||||
{
|
{
|
||||||
get => isValidMod;
|
get => isValidMod;
|
||||||
@ -46,11 +54,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether configurable <see cref="Mod"/>s can be configured by the local user.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual bool AllowCustomisation => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
|
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,18 +61,32 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys);
|
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys);
|
||||||
|
|
||||||
|
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
|
||||||
|
|
||||||
|
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => createDefaultFooterButtons();
|
||||||
|
|
||||||
private readonly BindableBool customisationVisible = new BindableBool();
|
private readonly BindableBool customisationVisible = new BindableBool();
|
||||||
|
|
||||||
private DifficultyMultiplierDisplay? multiplierDisplay;
|
|
||||||
private ModSettingsArea modSettingsArea = null!;
|
private ModSettingsArea modSettingsArea = null!;
|
||||||
private ColumnScrollContainer columnScroll = null!;
|
private ColumnScrollContainer columnScroll = null!;
|
||||||
private ColumnFlowContainer columnFlow = null!;
|
private ColumnFlowContainer columnFlow = null!;
|
||||||
|
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
|
||||||
|
private ShearedButton backButton = null!;
|
||||||
|
|
||||||
|
private DifficultyMultiplierDisplay? multiplierDisplay;
|
||||||
|
|
||||||
|
private ShearedToggleButton? customisationButton;
|
||||||
|
|
||||||
|
protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||||
|
: base(colourScheme)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Header.Title = "Mod Select";
|
Header.Title = ModSelectScreenStrings.ModSelectTitle;
|
||||||
Header.Description = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun.";
|
Header.Description = ModSelectScreenStrings.ModSelectDescription;
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
@ -94,6 +111,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING,
|
Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING,
|
||||||
|
Bottom = PADDING
|
||||||
},
|
},
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
@ -111,7 +129,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Shear = new Vector2(SHEAR, 0),
|
Shear = new Vector2(SHEAR, 0),
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Spacing = new Vector2(10, 0),
|
|
||||||
Margin = new MarginPadding { Horizontal = 70 },
|
Margin = new MarginPadding { Horizontal = 70 },
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
@ -144,31 +161,35 @@ namespace osu.Game.Overlays.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AllowCustomisation)
|
FooterContent.Child = footerButtonFlow = new FillFlowContainer<ShearedButton>
|
||||||
{
|
|
||||||
Footer.Add(new ShearedToggleButton(200)
|
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Margin = new MarginPadding { Vertical = PADDING, Left = 70 },
|
Padding = new MarginPadding
|
||||||
Text = "Mod Customisation",
|
|
||||||
Active = { BindTarget = customisationVisible }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
|
||||||
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.X,
|
Vertical = PADDING,
|
||||||
RelativeSizeAxes = Axes.Y,
|
Horizontal = 70
|
||||||
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
|
},
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
ChildrenEnumerable = CreateFooterButtons().Prepend(backButton = new ShearedButton(BUTTON_WIDTH)
|
||||||
|
{
|
||||||
|
Text = CommonStrings.Back,
|
||||||
|
Action = Hide,
|
||||||
|
DarkerColour = colours.Pink2,
|
||||||
|
LighterColour = colours.Pink1
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true);
|
||||||
|
|
||||||
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
||||||
|
|
||||||
SelectedMods.BindValueChanged(val =>
|
SelectedMods.BindValueChanged(val =>
|
||||||
@ -186,8 +207,66 @@ namespace osu.Game.Overlays.Mods
|
|||||||
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||||
|
|
||||||
updateAvailableMods();
|
updateAvailableMods();
|
||||||
|
|
||||||
|
// Start scrolled slightly to the right to give the user a sense that
|
||||||
|
// there is more horizontal content available.
|
||||||
|
ScheduleAfterChildren(() =>
|
||||||
|
{
|
||||||
|
columnScroll.ScrollTo(200, false);
|
||||||
|
columnScroll.ScrollToStart();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select all visible mods in all columns.
|
||||||
|
/// </summary>
|
||||||
|
protected void SelectAll()
|
||||||
|
{
|
||||||
|
foreach (var column in columnFlow.Columns)
|
||||||
|
column.SelectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deselect all visible mods in all columns.
|
||||||
|
/// </summary>
|
||||||
|
protected void DeselectAll()
|
||||||
|
{
|
||||||
|
foreach (var column in columnFlow.Columns)
|
||||||
|
column.DeselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
||||||
|
{
|
||||||
|
var column = CreateModColumn(modType, toggleKeys).With(column =>
|
||||||
|
{
|
||||||
|
column.Filter = IsValidMod;
|
||||||
|
// spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden.
|
||||||
|
column.Margin = new MarginPadding { Right = 10 };
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ColumnDimContainer(column)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RequestScroll = col => columnScroll.ScrollIntoView(col, extraScroll: 140),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShearedButton[] createDefaultFooterButtons()
|
||||||
|
=> new[]
|
||||||
|
{
|
||||||
|
customisationButton = new ShearedToggleButton(BUTTON_WIDTH)
|
||||||
|
{
|
||||||
|
Text = ModSelectScreenStrings.ModCustomisation,
|
||||||
|
Active = { BindTarget = customisationVisible }
|
||||||
|
},
|
||||||
|
new ShearedButton(BUTTON_WIDTH)
|
||||||
|
{
|
||||||
|
Text = CommonStrings.DeselectAll,
|
||||||
|
Action = DeselectAll
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private void updateMultiplier()
|
private void updateMultiplier()
|
||||||
{
|
{
|
||||||
if (multiplierDisplay == null)
|
if (multiplierDisplay == null)
|
||||||
@ -209,7 +288,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
|
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
|
||||||
{
|
{
|
||||||
if (!AllowCustomisation)
|
if (customisationButton == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool anyCustomisableMod = false;
|
bool anyCustomisableMod = false;
|
||||||
@ -243,6 +322,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic);
|
MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic);
|
||||||
|
|
||||||
|
foreach (var button in footerButtonFlow)
|
||||||
|
{
|
||||||
|
if (button != customisationButton)
|
||||||
|
button.Enabled.Value = !customisationVisible.Value;
|
||||||
|
}
|
||||||
|
|
||||||
float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0;
|
float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0;
|
||||||
|
|
||||||
modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic);
|
modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic);
|
||||||
@ -264,13 +349,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray();
|
var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray();
|
||||||
|
|
||||||
if (candidateSelection.SequenceEqual(SelectedMods.Value))
|
// the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion.
|
||||||
|
// TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6
|
||||||
|
if (candidateSelection.SequenceEqual(SelectedMods.Value, new FuncEqualityComparer<Mod>(ReferenceEquals)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
|
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
|
#region Transition handling
|
||||||
|
|
||||||
|
private const float distance = 700;
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
@ -283,13 +372,26 @@ namespace osu.Game.Overlays.Mods
|
|||||||
.FadeIn(fade_in_duration / 2, Easing.OutQuint)
|
.FadeIn(fade_in_duration / 2, Easing.OutQuint)
|
||||||
.ScaleTo(1, fade_in_duration, Easing.OutElastic);
|
.ScaleTo(1, fade_in_duration, Easing.OutElastic);
|
||||||
|
|
||||||
|
int nonFilteredColumnCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < columnFlow.Count; i++)
|
for (int i = 0; i < columnFlow.Count; i++)
|
||||||
{
|
{
|
||||||
columnFlow[i].Column
|
var column = columnFlow[i].Column;
|
||||||
.TopLevelContent
|
|
||||||
.Delay(i * 30)
|
double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30;
|
||||||
.MoveToY(0, fade_in_duration, Easing.OutQuint)
|
double duration = column.AllFiltered.Value ? 0 : fade_in_duration;
|
||||||
.FadeIn(fade_in_duration, Easing.OutQuint);
|
float startingYPosition = 0;
|
||||||
|
if (!column.AllFiltered.Value)
|
||||||
|
startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||||
|
|
||||||
|
column.TopLevelContent
|
||||||
|
.MoveToY(startingYPosition)
|
||||||
|
.Delay(delay)
|
||||||
|
.MoveToY(0, duration, Easing.OutQuint)
|
||||||
|
.FadeIn(duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (!column.AllFiltered.Value)
|
||||||
|
nonFilteredColumnCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,30 +405,83 @@ namespace osu.Game.Overlays.Mods
|
|||||||
.FadeOut(fade_out_duration / 2, Easing.OutQuint)
|
.FadeOut(fade_out_duration / 2, Easing.OutQuint)
|
||||||
.ScaleTo(0.75f, fade_out_duration, Easing.OutQuint);
|
.ScaleTo(0.75f, fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
int nonFilteredColumnCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < columnFlow.Count; i++)
|
for (int i = 0; i < columnFlow.Count; i++)
|
||||||
{
|
{
|
||||||
const float distance = 700;
|
|
||||||
|
|
||||||
var column = columnFlow[i].Column;
|
var column = columnFlow[i].Column;
|
||||||
|
|
||||||
|
double duration = column.AllFiltered.Value ? 0 : fade_out_duration;
|
||||||
|
float newYPosition = 0;
|
||||||
|
if (!column.AllFiltered.Value)
|
||||||
|
newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||||
|
|
||||||
column.FlushPendingSelections();
|
column.FlushPendingSelections();
|
||||||
column.TopLevelContent
|
column.TopLevelContent
|
||||||
.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint)
|
.MoveToY(newYPosition, duration, Easing.OutQuint)
|
||||||
.FadeOut(fade_out_duration, Easing.OutQuint);
|
.FadeOut(duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (!column.AllFiltered.Value)
|
||||||
|
nonFilteredColumnCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Input handling
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
{
|
{
|
||||||
if (e.Action == GlobalAction.Back && customisationVisible.Value)
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
customisationVisible.Value = false;
|
case GlobalAction.Back:
|
||||||
|
// Pressing the back binding should only go back one step at a time.
|
||||||
|
hideOverlay(false);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// This is handled locally here because this overlay is being registered at the game level
|
||||||
|
// and therefore takes away keyboard focus from the screen stack.
|
||||||
|
case GlobalAction.ToggleModSelection:
|
||||||
|
case GlobalAction.Select:
|
||||||
|
{
|
||||||
|
// Pressing toggle or select should completely hide the overlay in one shot.
|
||||||
|
hideOverlay(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnPressed(e);
|
return base.OnPressed(e);
|
||||||
|
|
||||||
|
void hideOverlay(bool immediate)
|
||||||
|
{
|
||||||
|
if (customisationVisible.Value)
|
||||||
|
{
|
||||||
|
Debug.Assert(customisationButton != null);
|
||||||
|
customisationButton.TriggerClick();
|
||||||
|
|
||||||
|
if (!immediate)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backButton.TriggerClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Sample playback control
|
||||||
|
|
||||||
|
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool(true);
|
||||||
|
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
|
||||||
|
/// </summary>
|
||||||
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
||||||
{
|
{
|
||||||
public ColumnScrollContainer()
|
public ColumnScrollContainer()
|
||||||
@ -365,6 +520,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages padding and layout of mod columns.
|
||||||
|
/// </summary>
|
||||||
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
|
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
|
||||||
{
|
{
|
||||||
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
|
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
|
||||||
@ -401,11 +559,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates a column and provides dim and input blocking based on an externally managed "active" state.
|
||||||
|
/// </summary>
|
||||||
internal class ColumnDimContainer : Container
|
internal class ColumnDimContainer : Container
|
||||||
{
|
{
|
||||||
public ModColumn Column { get; }
|
public ModColumn Column { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks whether this column is in an interactive state. Generally only the case when the column is on-screen.
|
||||||
|
/// </summary>
|
||||||
public readonly Bindable<bool> Active = new BindableBool();
|
public readonly Bindable<bool> Active = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the column is clicked while not active, requesting a scroll to be performed to bring it on-screen.
|
||||||
|
/// </summary>
|
||||||
public Action<ColumnDimContainer>? RequestScroll { get; set; }
|
public Action<ColumnDimContainer>? RequestScroll { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -420,15 +588,20 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
Active.BindValueChanged(_ => updateDim(), true);
|
Active.BindValueChanged(_ => updateState());
|
||||||
|
Column.AllFiltered.BindValueChanged(_ => updateState(), true);
|
||||||
FinishTransforms();
|
FinishTransforms();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDim()
|
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning;
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
{
|
{
|
||||||
Colour4 targetColour;
|
Colour4 targetColour;
|
||||||
|
|
||||||
if (Active.Value)
|
Column.Alpha = Column.AllFiltered.Value ? 0 : 1;
|
||||||
|
|
||||||
|
if (Column.Active.Value)
|
||||||
targetColour = Colour4.White;
|
targetColour = Colour4.White;
|
||||||
else
|
else
|
||||||
targetColour = IsHovered ? colours.GrayC : colours.Gray8;
|
targetColour = IsHovered ? colours.GrayC : colours.Gray8;
|
||||||
@ -447,17 +620,20 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
base.OnHover(e);
|
base.OnHover(e);
|
||||||
updateDim();
|
updateState();
|
||||||
return Active.Value;
|
return Active.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
updateDim();
|
updateState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A container which blocks and handles input, managing the "return from customisation" state change.
|
||||||
|
/// </summary>
|
||||||
private class ClickToReturnContainer : Container
|
private class ClickToReturnContainer : Container
|
||||||
{
|
{
|
||||||
public BindableBool HandleMouse { get; } = new BindableBool();
|
public BindableBool HandleMouse { get; } = new BindableBool();
|
||||||
|
@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
new[] { Empty() },
|
new[] { Empty() },
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new NestedVerticalScrollContainer
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ClampExtension = 100,
|
ClampExtension = 100,
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container.
|
|
||||||
/// </summary>
|
|
||||||
public class NestedVerticalScrollContainer : OsuScrollContainer
|
|
||||||
{
|
|
||||||
private ModSelectScreen.ColumnScrollContainer? parentScrollContainer;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
parentScrollContainer = this.FindClosestParent<ModSelectScreen.ColumnScrollContainer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
|
||||||
{
|
|
||||||
if (parentScrollContainer == null)
|
|
||||||
return base.OnScroll(e);
|
|
||||||
|
|
||||||
bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight);
|
|
||||||
bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft);
|
|
||||||
|
|
||||||
// If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view).
|
|
||||||
if (!topRightInView || !bottomLeftInView)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd();
|
|
||||||
bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0;
|
|
||||||
|
|
||||||
// If at either of our extents, delegate scroll to the horizontal parent container.
|
|
||||||
if (scrollingPastStart || scrollingPastEnd)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return base.OnScroll(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,17 +51,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected Container FooterContent { get; private set; }
|
protected Container FooterContent { get; private set; }
|
||||||
|
|
||||||
protected abstract OverlayColourScheme ColourScheme { get; }
|
|
||||||
|
|
||||||
protected override bool StartHidden => true;
|
protected override bool StartHidden => true;
|
||||||
|
|
||||||
protected override bool BlockNonPositionalInput => true;
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
protected ShearedOverlayContainer()
|
protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
ColourProvider = new OverlayColourProvider(ColourScheme);
|
ColourProvider = new OverlayColourProvider(colourScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -12,6 +12,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
public class UserModSelectScreen : ModSelectScreen
|
public class UserModSelectScreen : ModSelectScreen
|
||||||
{
|
{
|
||||||
|
public UserModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||||
|
: base(colourScheme)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys);
|
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys);
|
||||||
|
|
||||||
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
|
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
|
||||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private void updateProgress(APIUser user)
|
private void updateProgress(APIUser user)
|
||||||
{
|
{
|
||||||
levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0;
|
levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0;
|
||||||
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default(LocalisableString);
|
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Rankings
|
|||||||
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
|
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
|
||||||
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
|
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
|
||||||
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
|
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
|
||||||
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default(LocalisableString);
|
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");
|
private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");
|
||||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables
|
|||||||
|
|
||||||
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
|
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
|
||||||
{
|
{
|
||||||
new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString), }
|
new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default, }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,18 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool UserPlayable { get; }
|
bool UserPlayable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this mod is valid for multiplayer matches.
|
||||||
|
/// Should be <c>false</c> for mods that make gameplay duration dependent on user input (e.g. <see cref="ModAdaptiveSpeed"/>).
|
||||||
|
/// </summary>
|
||||||
|
bool ValidForMultiplayer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this mod is valid as a free mod in multiplayer matches.
|
||||||
|
/// Should be <c>false</c> for mods that affect the gameplay duration (e.g. <see cref="ModRateAdjust"/> and <see cref="ModTimeRamp"/>).
|
||||||
|
/// </summary>
|
||||||
|
bool ValidForMultiplayerAsFreeMod { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -94,6 +94,12 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual bool UserPlayable => true;
|
public virtual bool UserPlayable => true;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual bool ValidForMultiplayer => true;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual bool ValidForMultiplayerAsFreeMod => true;
|
||||||
|
|
||||||
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
|
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
|
||||||
public virtual bool Ranked => false;
|
public virtual bool Ranked => false;
|
||||||
|
|
||||||
|
@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override bool ValidForMultiplayer => false;
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) };
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public bool RestartOnFail => false;
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
public override bool UserPlayable => false;
|
public override bool UserPlayable => false;
|
||||||
|
public override bool ValidForMultiplayer => false;
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
public abstract class ModRateAdjust : Mod, IApplicableToRate
|
public abstract class ModRateAdjust : Mod, IApplicableToRate
|
||||||
{
|
{
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
|
||||||
public abstract BindableNumber<double> SpeedChange { get; }
|
public abstract BindableNumber<double> SpeedChange { get; }
|
||||||
|
|
||||||
public virtual void ApplyToTrack(ITrack track)
|
public virtual void ApplyToTrack(ITrack track)
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||||
public abstract BindableBool AdjustPitch { get; }
|
public abstract BindableBool AdjustPitch { get; }
|
||||||
|
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) };
|
||||||
|
|
||||||
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
|
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
|
||||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override double ScoreMultiplier => 0;
|
public override double ScoreMultiplier => 0;
|
||||||
|
|
||||||
public override bool UserPlayable => false;
|
public override bool UserPlayable => false;
|
||||||
|
public override bool ValidForMultiplayer => false;
|
||||||
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
|
||||||
public override ModType Type => ModType.System;
|
public override ModType Type => ModType.System;
|
||||||
|
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -51,14 +55,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
float diameter = (i + 1) * DistanceBetweenTicks * 2;
|
float diameter = (i + 1) * DistanceBetweenTicks * 2;
|
||||||
|
|
||||||
AddInternal(new CircularProgress
|
AddInternal(new Ring(ReferenceObject, GetColourForIndexFromPlacement(i))
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Position = StartPosition,
|
Position = StartPosition,
|
||||||
Current = { Value = 1 },
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(diameter),
|
Size = new Vector2(diameter),
|
||||||
InnerRadius = 4 * 1f / diameter,
|
InnerRadius = 4 * 1f / diameter,
|
||||||
Colour = GetColourForIndexFromPlacement(i)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,5 +102,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
return (snappedPosition, snappedTime);
|
return (snappedPosition, snappedTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Ring : CircularProgress
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private EditorClock editorClock { get; set; }
|
||||||
|
|
||||||
|
private readonly HitObject referenceObject;
|
||||||
|
|
||||||
|
private readonly Color4 baseColour;
|
||||||
|
|
||||||
|
public Ring(HitObject referenceObject, Color4 baseColour)
|
||||||
|
{
|
||||||
|
this.referenceObject = referenceObject;
|
||||||
|
|
||||||
|
Colour = this.baseColour = baseColour;
|
||||||
|
|
||||||
|
Current.Value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (editorClock == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value;
|
||||||
|
double timeFromReferencePoint = editorClock.CurrentTime - referenceObject.GetEndTime();
|
||||||
|
|
||||||
|
float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, timeFromReferencePoint)
|
||||||
|
* distanceSpacingMultiplier;
|
||||||
|
|
||||||
|
float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1);
|
||||||
|
|
||||||
|
Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -135,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="placementIndex">The 0-based beat index from the point of placement.</param>
|
/// <param name="placementIndex">The 0-based beat index from the point of placement.</param>
|
||||||
/// <returns>The applicable colour.</returns>
|
/// <returns>The applicable colour.</returns>
|
||||||
protected ColourInfo GetColourForIndexFromPlacement(int placementIndex)
|
protected Color4 GetColourForIndexFromPlacement(int placementIndex)
|
||||||
{
|
{
|
||||||
var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
|
var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
|
||||||
double beatLength = timingPoint.BeatLength / beatDivisor.Value;
|
double beatLength = timingPoint.BeatLength / beatDivisor.Value;
|
||||||
@ -144,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
|
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
|
||||||
|
|
||||||
int repeatIndex = placementIndex / beatDivisor.Value;
|
int repeatIndex = placementIndex / beatDivisor.Value;
|
||||||
return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1));
|
return colour.Opacity(0.5f / (repeatIndex + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void updateTooltipText()
|
private void updateTooltipText()
|
||||||
{
|
{
|
||||||
TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default(LocalisableString);
|
TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -50,7 +51,6 @@ using osuTK.Input;
|
|||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
{
|
{
|
||||||
[Cached(typeof(IBeatSnapProvider))]
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
|
||||||
[Cached]
|
[Cached]
|
||||||
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler
|
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler
|
||||||
{
|
{
|
||||||
|
@ -87,6 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#pragma warning disable IDE0055 // Indentation of commented code
|
||||||
// case MatchType.TagCoop:
|
// case MatchType.TagCoop:
|
||||||
// return new SpriteIcon
|
// return new SpriteIcon
|
||||||
// {
|
// {
|
||||||
@ -125,6 +126,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,15 +2,19 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
{
|
{
|
||||||
public class FreeModSelectScreen : ModSelectScreen
|
public class FreeModSelectScreen : ModSelectScreen
|
||||||
{
|
{
|
||||||
protected override bool AllowCustomisation => false;
|
|
||||||
protected override bool ShowTotalMultiplier => false;
|
protected override bool ShowTotalMultiplier => false;
|
||||||
|
|
||||||
public new Func<Mod, bool> IsValidMod
|
public new Func<Mod, bool> IsValidMod
|
||||||
@ -20,10 +24,29 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FreeModSelectScreen()
|
public FreeModSelectScreen()
|
||||||
|
: base(OverlayColourScheme.Plum)
|
||||||
{
|
{
|
||||||
IsValidMod = _ => true;
|
IsValidMod = _ => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
|
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
|
||||||
|
|
||||||
|
protected override IEnumerable<ShearedButton> CreateFooterButtons() => new[]
|
||||||
|
{
|
||||||
|
new ShearedButton(BUTTON_WIDTH)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = CommonStrings.SelectAll,
|
||||||
|
Action = SelectAll
|
||||||
|
},
|
||||||
|
new ShearedButton(BUTTON_WIDTH)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = CommonStrings.DeselectAll,
|
||||||
|
Action = DeselectAll
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -57,6 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
|
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IOverlayManager overlayManager { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
@ -77,7 +81,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
public readonly Room Room;
|
public readonly Room Room;
|
||||||
private readonly bool allowEdit;
|
private readonly bool allowEdit;
|
||||||
|
|
||||||
private ModSelectOverlay userModsSelectOverlay;
|
private ModSelectScreen userModsSelectOverlay;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable userModsSelectOverlayRegistration;
|
||||||
|
|
||||||
private RoomSettingsOverlay settingsOverlay;
|
private RoomSettingsOverlay settingsOverlay;
|
||||||
private Drawable mainContent;
|
private Drawable mainContent;
|
||||||
|
|
||||||
@ -180,11 +188,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
|
||||||
{
|
|
||||||
SelectedMods = { BindTarget = UserMods },
|
|
||||||
IsValidMod = _ => false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,6 +230,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LoadComponent(userModsSelectOverlay = new UserModSelectScreen(OverlayColourScheme.Plum)
|
||||||
|
{
|
||||||
|
SelectedMods = { BindTarget = UserMods },
|
||||||
|
IsValidMod = _ => false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -254,6 +263,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
|
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
|
||||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
|
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
|
||||||
|
|
||||||
|
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -298,7 +309,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
public override void OnSuspending(ScreenTransitionEvent e)
|
public override void OnSuspending(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
endHandlingTrack();
|
onLeaving();
|
||||||
base.OnSuspending(e);
|
base.OnSuspending(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
RoomManager?.PartRoom();
|
RoomManager?.PartRoom();
|
||||||
Mods.Value = Array.Empty<Mod>();
|
Mods.Value = Array.Empty<Mod>();
|
||||||
|
|
||||||
endHandlingTrack();
|
onLeaving();
|
||||||
|
|
||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
}
|
}
|
||||||
@ -412,6 +423,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
Beatmap.BindValueChanged(applyLoopingToTrack, true);
|
Beatmap.BindValueChanged(applyLoopingToTrack, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onLeaving()
|
||||||
|
{
|
||||||
|
userModsSelectOverlay.Hide();
|
||||||
|
endHandlingTrack();
|
||||||
|
}
|
||||||
|
|
||||||
private void endHandlingTrack()
|
private void endHandlingTrack()
|
||||||
{
|
{
|
||||||
Beatmap.ValueChanged -= applyLoopingToTrack;
|
Beatmap.ValueChanged -= applyLoopingToTrack;
|
||||||
@ -459,5 +476,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
public class UserModSelectButton : PurpleTriangleButton
|
public class UserModSelectButton : PurpleTriangleButton
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
userModsSelectOverlayRegistration?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||||
|
|
||||||
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust);
|
protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.ValidForMultiplayer;
|
||||||
|
|
||||||
|
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidForMultiplayerAsFreeMod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -45,7 +46,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
|
||||||
private readonly Room room;
|
private readonly Room room;
|
||||||
|
|
||||||
private WorkingBeatmap initialBeatmap;
|
private WorkingBeatmap initialBeatmap;
|
||||||
@ -53,13 +53,16 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
private IReadOnlyList<Mod> initialMods;
|
private IReadOnlyList<Mod> initialMods;
|
||||||
private bool itemSelected;
|
private bool itemSelected;
|
||||||
|
|
||||||
|
private readonly FreeModSelectScreen freeModSelectOverlay;
|
||||||
|
private IDisposable freeModSelectOverlayRegistration;
|
||||||
|
|
||||||
protected OnlinePlaySongSelect(Room room)
|
protected OnlinePlaySongSelect(Room room)
|
||||||
{
|
{
|
||||||
this.room = room;
|
this.room = room;
|
||||||
|
|
||||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||||
|
|
||||||
freeModSelectOverlay = new FreeModSelectOverlay
|
freeModSelectOverlay = new FreeModSelectScreen
|
||||||
{
|
{
|
||||||
SelectedMods = { BindTarget = FreeMods },
|
SelectedMods = { BindTarget = FreeMods },
|
||||||
IsValidMod = IsValidFreeMod,
|
IsValidMod = IsValidFreeMod,
|
||||||
@ -75,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
initialRuleset = Ruleset.Value;
|
initialRuleset = Ruleset.Value;
|
||||||
initialMods = Mods.Value.ToList();
|
initialMods = Mods.Value.ToList();
|
||||||
|
|
||||||
FooterPanels.Add(freeModSelectOverlay);
|
LoadComponent(freeModSelectOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -94,6 +97,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
Mods.BindValueChanged(onModsChanged);
|
Mods.BindValueChanged(onModsChanged);
|
||||||
Ruleset.BindValueChanged(onRulesetChanged);
|
Ruleset.BindValueChanged(onRulesetChanged);
|
||||||
|
|
||||||
|
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelectOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
@ -150,10 +155,12 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
Mods.Value = initialMods;
|
Mods.Value = initialMods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
freeModSelectOverlay.Hide();
|
||||||
|
|
||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay
|
protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum)
|
||||||
{
|
{
|
||||||
IsValidMod = IsValidMod
|
IsValidMod = IsValidMod
|
||||||
};
|
};
|
||||||
@ -182,5 +189,12 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
private bool checkCompatibleFreeMod(Mod mod)
|
private bool checkCompatibleFreeMod(Mod mod)
|
||||||
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
|
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
|
||||||
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
|
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
freeModSelectOverlayRegistration?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -37,7 +38,6 @@ using osuTK.Graphics;
|
|||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
|
||||||
public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo
|
public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -85,7 +85,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
InternalChild = scroll = new Scroll
|
InternalChild = scroll = new Scroll
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel.
|
|
||||||
Child = flow = new Flow
|
Child = flow = new Flow
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -359,11 +358,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float? InstantScrollTarget;
|
public float? InstantScrollTarget;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this container should handle scroll trigger events.
|
|
||||||
/// </summary>
|
|
||||||
public Func<bool> HandleScroll;
|
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
if (InstantScrollTarget != null)
|
if (InstantScrollTarget != null)
|
||||||
@ -374,10 +368,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandlePositionalInput => HandleScroll();
|
|
||||||
|
|
||||||
public override bool HandleNonPositionalInput => HandleScroll();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -101,7 +102,7 @@ namespace osu.Game.Screens.Select
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private LegacyImportManager legacyImportManager { get; set; }
|
private LegacyImportManager legacyImportManager { get; set; }
|
||||||
|
|
||||||
protected ModSelectOverlay ModSelect { get; private set; }
|
protected ModSelectScreen ModSelect { get; private set; }
|
||||||
|
|
||||||
protected Sample SampleConfirm { get; private set; }
|
protected Sample SampleConfirm { get; private set; }
|
||||||
|
|
||||||
@ -116,9 +117,15 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private double audioFeedbackLastPlaybackTime;
|
private double audioFeedbackLastPlaybackTime;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable modSelectOverlayRegistration;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
internal IOverlayManager OverlayManager { get; private set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
|
private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
|
||||||
{
|
{
|
||||||
@ -251,19 +258,6 @@ namespace osu.Game.Screens.Select
|
|||||||
if (ShowFooter)
|
if (ShowFooter)
|
||||||
{
|
{
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
|
||||||
new GridContainer // used for max height implementation
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Relative, 1f, maxSize: ModSelectOverlay.HEIGHT + Footer.HEIGHT),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
null,
|
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
FooterPanels = new Container
|
FooterPanels = new Container
|
||||||
{
|
{
|
||||||
@ -274,16 +268,16 @@ namespace osu.Game.Screens.Select
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
BeatmapOptions = new BeatmapOptionsOverlay(),
|
BeatmapOptions = new BeatmapOptionsOverlay(),
|
||||||
ModSelect = CreateModSelectOverlay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Footer = new Footer()
|
Footer = new Footer(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preload the mod select overlay for later use in `LoadComplete()`.
|
||||||
|
// therein it will be registered at the `OsuGame` level to properly function as a blocking overlay.
|
||||||
|
LoadComponent(ModSelect = CreateModSelectOverlay());
|
||||||
|
|
||||||
if (Footer != null)
|
if (Footer != null)
|
||||||
{
|
{
|
||||||
foreach (var (button, overlay) in CreateFooterButtons())
|
foreach (var (button, overlay) in CreateFooterButtons())
|
||||||
@ -317,6 +311,13 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the buttons to be displayed in the footer.
|
/// Creates the buttons to be displayed in the footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -332,7 +333,7 @@ namespace osu.Game.Screens.Select
|
|||||||
(new FooterButtonOptions(), BeatmapOptions)
|
(new FooterButtonOptions(), BeatmapOptions)
|
||||||
};
|
};
|
||||||
|
|
||||||
protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay();
|
protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen();
|
||||||
|
|
||||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||||
{
|
{
|
||||||
@ -658,6 +659,7 @@ namespace osu.Game.Screens.Select
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
beatmapInfoWedge.Hide();
|
beatmapInfoWedge.Hide();
|
||||||
|
ModSelect.Hide();
|
||||||
|
|
||||||
this.FadeOut(100);
|
this.FadeOut(100);
|
||||||
|
|
||||||
@ -716,6 +718,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (music != null)
|
if (music != null)
|
||||||
music.TrackChanged -= ensureTrackLooping;
|
music.TrackChanged -= ensureTrackLooping;
|
||||||
|
|
||||||
|
modSelectOverlayRegistration?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
|
|
||||||
@ -15,11 +18,12 @@ namespace osu.Game.Tests.Visual
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
|
/// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ScreenTestScene : OsuManualInputManagerTestScene
|
public abstract class ScreenTestScene : OsuManualInputManagerTestScene, IOverlayManager
|
||||||
{
|
{
|
||||||
protected readonly OsuScreenStack Stack;
|
protected readonly OsuScreenStack Stack;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
private readonly Container overlayContent;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
@ -36,7 +40,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
DialogOverlay = new DialogOverlay()
|
overlayContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = DialogOverlay = new DialogOverlay()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
|
Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
|
||||||
@ -65,5 +73,26 @@ namespace osu.Game.Tests.Visual
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IOverlayManager
|
||||||
|
|
||||||
|
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode { get; } = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||||
|
|
||||||
|
// in the blocking methods below it is important to be careful about threading (e.g. use `Expire()` rather than `Remove()`, and schedule transforms),
|
||||||
|
// because in the worst case the clean-up methods could be called from async disposal.
|
||||||
|
|
||||||
|
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||||
|
{
|
||||||
|
overlayContent.Add(overlayContainer);
|
||||||
|
return new InvokeOnDisposal(() => overlayContainer.Expire());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
|
||||||
|
=> Schedule(() => Stack.FadeColour(OsuColour.Gray(0.5f), 500, Easing.OutQuint));
|
||||||
|
|
||||||
|
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay)
|
||||||
|
=> Schedule(() => Stack.FadeColour(Colour4.White, 500, Easing.OutQuint));
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,22 +106,69 @@ namespace osu.Game.Utils
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check the provided combination of mods are valid for a local gameplay session.
|
/// Checks that all <see cref="Mod"/>s in a combination are valid for a local gameplay session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mods">The mods to check.</param>
|
/// <param name="mods">The mods to check.</param>
|
||||||
/// <param name="invalidMods">Invalid mods, if any were found. Can be null if all mods were valid.</param>
|
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||||
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||||
public static bool CheckValidForGameplay(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
public static bool CheckValidForGameplay(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||||
{
|
{
|
||||||
mods = mods.ToArray();
|
mods = mods.ToArray();
|
||||||
|
|
||||||
// exclude multi mods from compatibility checks.
|
// checking compatibility of multi mods would try to flatten them and return incompatible mods.
|
||||||
// the loop below automatically marks all multi mods as not valid for gameplay anyway.
|
// in gameplay context, we never want MultiMod selected in the first place, therefore check against it first.
|
||||||
CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods);
|
if (!checkValid(mods, m => !(m is MultiMod), out invalidMods))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckCompatibleSet(mods, out invalidMods))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation, out invalidMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks that all <see cref="Mod"/>s in a combination are valid as "required mods" in a multiplayer match session.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mods">The mods to check.</param>
|
||||||
|
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||||
|
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||||
|
public static bool CheckValidRequiredModsForMultiplayer(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||||
|
{
|
||||||
|
mods = mods.ToArray();
|
||||||
|
|
||||||
|
// checking compatibility of multi mods would try to flatten them and return incompatible mods.
|
||||||
|
// in gameplay context, we never want MultiMod selected in the first place, therefore check against it first.
|
||||||
|
if (!checkValid(mods, m => !(m is MultiMod), out invalidMods))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckCompatibleSet(mods, out invalidMods))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayer, out invalidMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks that all <see cref="Mod"/>s in a combination are valid as "free mods" in a multiplayer match session.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that this does not check compatibility between mods,
|
||||||
|
/// given that the passed mods are expected to be the ones to be allowed for the multiplayer match,
|
||||||
|
/// not to be confused with the list of mods the user currently has selected for the multiplayer match.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="mods">The mods to check.</param>
|
||||||
|
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||||
|
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||||
|
public static bool CheckValidFreeModsForMultiplayer(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||||
|
=> checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayerAsFreeMod && !(m is MultiMod), out invalidMods);
|
||||||
|
|
||||||
|
private static bool checkValid(IEnumerable<Mod> mods, Predicate<Mod> valid, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||||
|
{
|
||||||
|
mods = mods.ToArray();
|
||||||
|
invalidMods = null;
|
||||||
|
|
||||||
foreach (var mod in mods)
|
foreach (var mod in mods)
|
||||||
{
|
{
|
||||||
if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod)
|
if (!valid(mod))
|
||||||
{
|
{
|
||||||
invalidMods ??= new List<Mod>();
|
invalidMods ??= new List<Mod>();
|
||||||
invalidMods.Add(mod);
|
invalidMods.Add(mod);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user