mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:23:22 +08:00
Merge branch 'master' into catch-editor-per-object-sv
This commit is contained in:
commit
c7e9bd7751
@ -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. -->
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
|||||||
using osu.Game.Rulesets.Taiko;
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.OnlinePlay;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
@ -176,5 +177,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
.ChildrenOfType<ModPanel>()
|
.ChildrenOfType<ModPanel>()
|
||||||
.SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
|
.SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNextPlaylistItemSelectedAfterCompletion()
|
||||||
|
{
|
||||||
|
AddStep("add two playlist items", () =>
|
||||||
|
{
|
||||||
|
SelectedRoom.Value.Playlist.AddRange(new[]
|
||||||
|
{
|
||||||
|
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
|
||||||
|
{
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||||
|
},
|
||||||
|
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
|
||||||
|
{
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
|
||||||
|
|
||||||
|
AddUntilStep("wait for join", () => RoomJoined);
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
|
||||||
|
AddStep("change user to loaded", () => MultiplayerClient.ChangeState(MultiplayerUserState.Loaded));
|
||||||
|
AddUntilStep("user playing", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Playing);
|
||||||
|
AddStep("abort gameplay", () => MultiplayerClient.AbortGameplay());
|
||||||
|
|
||||||
|
AddUntilStep("last playlist item selected", () =>
|
||||||
|
{
|
||||||
|
var lastItem = this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(p => p.Item.ID == MultiplayerClient.APIRoom?.Playlist.Last().ID);
|
||||||
|
return lastItem.IsSelectedItem;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -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;
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -451,6 +451,36 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden);
|
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));
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
|
@ -55,8 +55,18 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
@ -339,6 +349,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
panel.ApplyFilter(Filter);
|
panel.ApplyFilter(Filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value);
|
||||||
|
|
||||||
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
||||||
{
|
{
|
||||||
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
||||||
|
@ -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
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
using osu.Framework.Lists;
|
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;
|
||||||
@ -27,7 +28,7 @@ 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 const int BUTTON_WIDTH = 200;
|
protected const int BUTTON_WIDTH = 200;
|
||||||
|
|
||||||
@ -128,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[]
|
||||||
{
|
{
|
||||||
@ -188,6 +188,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
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 =>
|
||||||
@ -205,6 +207,14 @@ 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>
|
/// <summary>
|
||||||
@ -226,12 +236,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
||||||
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
|
{
|
||||||
|
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,
|
AutoSizeAxes = Axes.X,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
|
RequestScroll = col => columnScroll.ScrollIntoView(col, extraScroll: 140),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private ShearedButton[] createDefaultFooterButtons()
|
private ShearedButton[] createDefaultFooterButtons()
|
||||||
=> new[]
|
=> new[]
|
||||||
@ -340,6 +359,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
#region Transition handling
|
#region Transition handling
|
||||||
|
|
||||||
|
private const float distance = 700;
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
const double fade_in_duration = 400;
|
const double fade_in_duration = 400;
|
||||||
@ -351,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,16 +405,24 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,35 +435,50 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (e.Repeat)
|
if (e.Repeat)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 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.
|
|
||||||
if (e.Action == GlobalAction.Back)
|
|
||||||
{
|
|
||||||
if (customisationVisible.Value)
|
|
||||||
customisationVisible.Value = false;
|
|
||||||
else
|
|
||||||
backButton.TriggerClick();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
|
case GlobalAction.Back:
|
||||||
|
// Pressing the back binding should only go back one step at a time.
|
||||||
|
hideOverlay(false);
|
||||||
|
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.ToggleModSelection:
|
||||||
case GlobalAction.Select:
|
case GlobalAction.Select:
|
||||||
{
|
{
|
||||||
if (customisationVisible.Value)
|
// Pressing toggle or select should completely hide the overlay in one shot.
|
||||||
customisationVisible.Value = false;
|
hideOverlay(true);
|
||||||
Hide();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
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
|
#endregion
|
||||||
|
|
||||||
|
#region Sample playback control
|
||||||
|
|
||||||
|
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool(true);
|
||||||
|
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
|
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -531,17 +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();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning;
|
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning;
|
||||||
|
|
||||||
private void updateDim()
|
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;
|
||||||
@ -560,14 +620,14 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -65,6 +65,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
public readonly PlaylistItem Item;
|
public readonly PlaylistItem Item;
|
||||||
|
|
||||||
|
public bool IsSelectedItem => SelectedItem.Value?.ID == Item.ID;
|
||||||
|
|
||||||
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
|
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
|
||||||
private readonly IBindable<bool> valid = new Bindable<bool>();
|
private readonly IBindable<bool> valid = new Bindable<bool>();
|
||||||
|
|
||||||
@ -128,12 +130,10 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
SelectedItem.BindValueChanged(selected =>
|
SelectedItem.BindValueChanged(selected =>
|
||||||
{
|
{
|
||||||
bool isCurrent = selected.NewValue == Model;
|
|
||||||
|
|
||||||
if (!valid.Value)
|
if (!valid.Value)
|
||||||
{
|
{
|
||||||
// Don't allow selection when not valid.
|
// Don't allow selection when not valid.
|
||||||
if (isCurrent)
|
if (IsSelectedItem)
|
||||||
{
|
{
|
||||||
SelectedItem.Value = selected.OldValue;
|
SelectedItem.Value = selected.OldValue;
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
maskingContainer.BorderThickness = isCurrent ? 5 : 0;
|
maskingContainer.BorderThickness = IsSelectedItem ? 5 : 0;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
|
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Utils
|
|||||||
|
|
||||||
var options = new SentryOptions
|
var options = new SentryOptions
|
||||||
{
|
{
|
||||||
Dsn = "https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255",
|
Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2",
|
||||||
Release = game.Version
|
Release = game.Version
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.10.0" />
|
<PackageReference Include="Realm" Version="10.10.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.509.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.14.1" />
|
<PackageReference Include="Sentry" Version="3.14.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.430.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.509.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.509.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user