mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +08:00
Merge branch 'master' into tourney-switching-ui
This commit is contained in:
commit
959696c296
@ -18,7 +18,7 @@
|
|||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Code Analysis">
|
<PropertyGroup Label="Code Analysis">
|
||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||||
@ -28,9 +28,17 @@
|
|||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
<!--
|
||||||
This is required due to https://github.com/NuGet/Home/issues/5740 -->
|
NU1701:
|
||||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
||||||
|
This is required due to https://github.com/NuGet/Home/issues/5740
|
||||||
|
|
||||||
|
CA9998:
|
||||||
|
Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated.
|
||||||
|
The entire package will be able to be removed after migrating to .NET 5,
|
||||||
|
as analysers are shipped as part of the .NET 5 SDK anyway.
|
||||||
|
-->
|
||||||
|
<NoWarn>$(NoWarn);NU1701;CA9998</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
@ -40,7 +48,7 @@
|
|||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<Company>ppy Pty Ltd</Company>
|
<Company>ppy Pty Ltd</Company>
|
||||||
<Copyright>Copyright (c) 2020 ppy Pty Ltd</Copyright>
|
<Copyright>Copyright (c) 2021 ppy Pty Ltd</Copyright>
|
||||||
<PackageTags>osu game</PackageTags>
|
<PackageTags>osu game</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
2
LICENCE
2
LICENCE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020 ppy Pty Ltd <contact@ppy.sh>.
|
Copyright (c) 2021 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1229.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.106.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||||
<releaseNotes>testing</releaseNotes>
|
<releaseNotes>testing</releaseNotes>
|
||||||
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2021 ppy Pty Ltd</copyright>
|
||||||
<language>en-AU</language>
|
<language>en-AU</language>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
difficulty.CircleSize = CircleSize.Value;
|
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||||
difficulty.ApproachRate = ApproachRate.Value;
|
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
// 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.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModDifficultyAdjust(),
|
Mod = new OsuModDifficultyAdjust(),
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
},
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
difficulty.CircleSize = CircleSize.Value;
|
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||||
difficulty.ApproachRate = ApproachRate.Value;
|
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,5 +246,32 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||||
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
|
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateCopyIsDeepClone()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
|
||||||
|
var cpiCopy = cpi.CreateCopy();
|
||||||
|
|
||||||
|
cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpiCopy.Groups.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpiCopy.TimingPoints.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(cpi.TimingPoints[0], Is.Not.SameAs(cpiCopy.TimingPoints[0]));
|
||||||
|
Assert.That(cpi.TimingPoints[0].BeatLengthBindable, Is.Not.SameAs(cpiCopy.TimingPoints[0].BeatLengthBindable));
|
||||||
|
|
||||||
|
Assert.That(cpi.TimingPoints[0].BeatLength, Is.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
|
||||||
|
|
||||||
|
cpi.TimingPoints[0].BeatLength = 800;
|
||||||
|
|
||||||
|
Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
private CreateMultiplayerMatchButton button;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("create button", () => Child = button = new CreateMultiplayerMatchButton
|
||||||
|
{
|
||||||
|
Width = 200,
|
||||||
|
Height = 100,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtonEnableStateChanges()
|
||||||
|
{
|
||||||
|
IDisposable joiningRoomOperation = null;
|
||||||
|
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
|
||||||
|
AddStep("begin joining room", () => joiningRoomOperation = OngoingOperationTracker.BeginOperation());
|
||||||
|
assertButtonEnableState(false);
|
||||||
|
|
||||||
|
AddStep("end joining room", () => joiningRoomOperation.Dispose());
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
|
||||||
|
AddStep("disconnect client", () => Client.Disconnect());
|
||||||
|
assertButtonEnableState(false);
|
||||||
|
|
||||||
|
AddStep("re-connect client", () => Client.Connect());
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertButtonEnableState(bool enabled)
|
||||||
|
=> AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => button.Enabled.Value == enabled);
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.OnlinePlay;
|
||||||
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;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -18,6 +20,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private MultiplayerMatchSubScreen screen;
|
private MultiplayerMatchSubScreen screen;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
|
||||||
|
|
||||||
public TestSceneMultiplayerMatchSubScreen()
|
public TestSceneMultiplayerMatchSubScreen()
|
||||||
: base(false)
|
: base(false)
|
||||||
{
|
{
|
||||||
|
@ -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 System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -30,6 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private BeatmapManager beatmaps;
|
private BeatmapManager beatmaps;
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
|
private IDisposable readyClickOperation;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -56,6 +59,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
||||||
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
|
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
OnReadyClick = async () =>
|
||||||
|
{
|
||||||
|
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
{
|
||||||
|
await Client.StartMatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Client.ToggleReady();
|
||||||
|
readyClickOperation.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -108,8 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
addClickButtonStep();
|
verifyGameplayStartFlow();
|
||||||
AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -124,8 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
|
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
|
||||||
|
|
||||||
addClickButtonStep();
|
verifyGameplayStartFlow();
|
||||||
AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -179,5 +193,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.MoveMouseTo(button);
|
InputManager.MoveMouseTo(button);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void verifyGameplayStartFlow()
|
||||||
|
{
|
||||||
|
addClickButtonStep();
|
||||||
|
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||||
|
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||||
|
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Select.Details;
|
using osu.Game.Screens.Select.Details;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -141,16 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("select changed Difficulty Adjust mod", () =>
|
AddStep("select changed Difficulty Adjust mod", () =>
|
||||||
{
|
{
|
||||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||||
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single();
|
var difficultyAdjustMod = ruleset.GetAllMods().OfType<OsuModDifficultyAdjust>().Single();
|
||||||
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
|
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
|
||||||
var adjustedDifficulty = new BeatmapDifficulty
|
|
||||||
{
|
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
|
||||||
CircleSize = originalDifficulty.CircleSize,
|
difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;
|
||||||
DrainRate = originalDifficulty.DrainRate - 0.5f,
|
difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f;
|
||||||
OverallDifficulty = originalDifficulty.OverallDifficulty,
|
|
||||||
ApproachRate = originalDifficulty.ApproachRate + 2.2f,
|
|
||||||
};
|
|
||||||
difficultyAdjustMod.ReadFromDifficulty(adjustedDifficulty);
|
|
||||||
SelectedMods.Value = new[] { difficultyAdjustMod };
|
SelectedMods.Value = new[] { difficultyAdjustMod };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -131,6 +131,18 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
|
AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExternallySetCustomizedMod()
|
||||||
|
{
|
||||||
|
AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
|
||||||
|
|
||||||
|
AddAssert("ensure button is selected and customized accordingly", () =>
|
||||||
|
{
|
||||||
|
var button = modSelect.GetModButton(SelectedMods.Value.Single());
|
||||||
|
return ((OsuModDoubleTime)button.SelectedMod).SpeedChange.Value == 1.01;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void testSingleMod(Mod mod)
|
private void testSingleMod(Mod mod)
|
||||||
{
|
{
|
||||||
selectNext(mod);
|
selectNext(mod);
|
||||||
|
65
osu.Game.Tournament/JsonPointConverter.cs
Normal file
65
osu.Game.Tournament/JsonPointConverter.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// We made a change from using SixLabors.ImageSharp.Point to System.Drawing.Point at some stage.
|
||||||
|
/// This handles converting to a standardised format on json serialize/deserialize operations.
|
||||||
|
/// </summary>
|
||||||
|
internal class JsonPointConverter : JsonConverter<Point>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
// use the format of LaborSharp's Point since it is nicer.
|
||||||
|
serializer.Serialize(writer, new { value.X, value.Y });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (reader.TokenType != JsonToken.StartObject)
|
||||||
|
{
|
||||||
|
// if there's no object present then this is using string representation (System.Drawing.Point serializes to "x,y")
|
||||||
|
string str = (string)reader.Value;
|
||||||
|
|
||||||
|
Debug.Assert(str != null);
|
||||||
|
|
||||||
|
return new PointConverter().ConvertFromString(str) as Point? ?? new Point();
|
||||||
|
}
|
||||||
|
|
||||||
|
var point = new Point();
|
||||||
|
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonToken.EndObject) break;
|
||||||
|
|
||||||
|
if (reader.TokenType == JsonToken.PropertyName)
|
||||||
|
{
|
||||||
|
var name = reader.Value?.ToString();
|
||||||
|
int? val = reader.ReadAsInt32();
|
||||||
|
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "X":
|
||||||
|
point.X = val.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Y":
|
||||||
|
point.Y = val.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,12 @@ using Newtonsoft.Json;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Tournament.IPC;
|
|
||||||
using osu.Game.Tournament.IO;
|
using osu.Game.Tournament.IO;
|
||||||
|
using osu.Game.Tournament.IPC;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -60,7 +60,7 @@ namespace osu.Game.Tournament
|
|||||||
{
|
{
|
||||||
using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open))
|
using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open))
|
||||||
using (var sr = new StreamReader(stream))
|
using (var sr = new StreamReader(stream))
|
||||||
ladder = JsonConvert.DeserializeObject<LadderInfo>(sr.ReadToEnd());
|
ladder = JsonConvert.DeserializeObject<LadderInfo>(sr.ReadToEnd(), new JsonPointConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
ladder ??= new LadderInfo();
|
ladder ??= new LadderInfo();
|
||||||
@ -251,6 +251,7 @@ namespace osu.Game.Tournament
|
|||||||
Formatting = Formatting.Indented,
|
Formatting = Formatting.Indented,
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||||
|
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,15 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
IBeatmap IBeatmap.Clone() => Clone();
|
IBeatmap IBeatmap.Clone() => Clone();
|
||||||
|
|
||||||
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
|
public Beatmap<T> Clone()
|
||||||
|
{
|
||||||
|
var clone = (Beatmap<T>)MemberwiseClone();
|
||||||
|
|
||||||
|
clone.ControlPointInfo = ControlPointInfo.CreateCopy();
|
||||||
|
// todo: deep clone other elements as required.
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Beatmap : Beatmap<HitObject>
|
public class Beatmap : Beatmap<HitObject>
|
||||||
|
@ -28,5 +28,21 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// <param name="existing">An existing control point to compare with.</param>
|
/// <param name="existing">An existing control point to compare with.</param>
|
||||||
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
||||||
public abstract bool IsRedundant(ControlPoint existing);
|
public abstract bool IsRedundant(ControlPoint existing);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an unbound copy of this control point.
|
||||||
|
/// </summary>
|
||||||
|
public ControlPoint CreateCopy()
|
||||||
|
{
|
||||||
|
var copy = (ControlPoint)Activator.CreateInstance(GetType());
|
||||||
|
|
||||||
|
copy.CopyFrom(this);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,5 +297,15 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ControlPointInfo CreateCopy()
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
foreach (var point in AllControlPoints)
|
||||||
|
controlPointInfo.Add(point.Time, point.CreateCopy());
|
||||||
|
|
||||||
|
return controlPointInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,5 +39,12 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public override bool IsRedundant(ControlPoint existing)
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
=> existing is DifficultyControlPoint existingDifficulty
|
=> existing is DifficultyControlPoint existingDifficulty
|
||||||
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
||||||
|
|
||||||
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier;
|
||||||
|
|
||||||
|
base.CopyFrom(other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,5 +50,13 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
&& existing is EffectControlPoint existingEffect
|
&& existing is EffectControlPoint existingEffect
|
||||||
&& KiaiMode == existingEffect.KiaiMode
|
&& KiaiMode == existingEffect.KiaiMode
|
||||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
||||||
|
|
||||||
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
||||||
|
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
|
||||||
|
|
||||||
|
base.CopyFrom(other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,5 +72,13 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
=> existing is SampleControlPoint existingSample
|
=> existing is SampleControlPoint existingSample
|
||||||
&& SampleBank == existingSample.SampleBank
|
&& SampleBank == existingSample.SampleBank
|
||||||
&& SampleVolume == existingSample.SampleVolume;
|
&& SampleVolume == existingSample.SampleVolume;
|
||||||
|
|
||||||
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
SampleVolume = ((SampleControlPoint)other).SampleVolume;
|
||||||
|
SampleBank = ((SampleControlPoint)other).SampleBank;
|
||||||
|
|
||||||
|
base.CopyFrom(other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,5 +69,13 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
// Timing points are never redundant as they can change the time signature.
|
// Timing points are never redundant as they can change the time signature.
|
||||||
public override bool IsRedundant(ControlPoint existing) => false;
|
public override bool IsRedundant(ControlPoint existing) => false;
|
||||||
|
|
||||||
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
TimeSignature = ((TimingControlPoint)other).TimeSignature;
|
||||||
|
BeatLength = ((TimingControlPoint)other).BeatLength;
|
||||||
|
|
||||||
|
base.CopyFrom(other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,13 +164,24 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
||||||
/// DO NOT USE THIS UNLESS 100% SURE.
|
/// DO NOT USE THIS UNLESS 100% SURE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float BpmMultiplier;
|
public float BpmMultiplier { get; private set; }
|
||||||
|
|
||||||
public LegacyDifficultyControlPoint(double beatLength)
|
public LegacyDifficultyControlPoint(double beatLength)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyDifficultyControlPoint()
|
||||||
{
|
{
|
||||||
SpeedMultiplierBindable.Precision = double.Epsilon;
|
SpeedMultiplierBindable.Precision = double.Epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
base.CopyFrom(other);
|
||||||
|
|
||||||
|
BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +203,13 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
=> base.IsRedundant(existing)
|
=> base.IsRedundant(existing)
|
||||||
&& existing is LegacySampleControlPoint existingSample
|
&& existing is LegacySampleControlPoint existingSample
|
||||||
&& CustomSampleBank == existingSample.CustomSampleBank;
|
&& CustomSampleBank == existingSample.CustomSampleBank;
|
||||||
|
|
||||||
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
{
|
||||||
|
base.CopyFrom(other);
|
||||||
|
|
||||||
|
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// 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.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -14,6 +16,7 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
Set(Static.LoginOverlayDisplayed, false);
|
Set(Static.LoginOverlayDisplayed, false);
|
||||||
Set(Static.MutedAudioNotificationShownOnce, false);
|
Set(Static.MutedAudioNotificationShownOnce, false);
|
||||||
|
Set(Static.LastHoverSoundPlaybackTime, (double?)null);
|
||||||
Set<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
Set<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,5 +31,11 @@ namespace osu.Game.Configuration
|
|||||||
/// Value under this lookup can be <c>null</c> if there are no backgrounds available (or API is not reachable).
|
/// Value under this lookup can be <c>null</c> if there are no backgrounds available (or API is not reachable).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SeasonalBackgrounds,
|
SeasonalBackgrounds,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last playback time in milliseconds of a hover sample (from <see cref="HoverSounds"/>).
|
||||||
|
/// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like <see cref="SettingsOverlay"/>.
|
||||||
|
/// </summary>
|
||||||
|
LastHoverSoundPlaybackTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,12 @@ using System.ComponentModel;
|
|||||||
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;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
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.Threading;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
@ -22,37 +23,40 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private SampleChannel sampleHover;
|
private SampleChannel sampleHover;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Length of debounce for hover sound playback, in milliseconds. Default is 50ms.
|
/// Length of debounce for hover sound playback, in milliseconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double HoverDebounceTime { get; } = 50;
|
public double HoverDebounceTime { get; } = 20;
|
||||||
|
|
||||||
protected readonly HoverSampleSet SampleSet;
|
protected readonly HoverSampleSet SampleSet;
|
||||||
|
|
||||||
|
private Bindable<double?> lastPlaybackTime;
|
||||||
|
|
||||||
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
||||||
{
|
{
|
||||||
SampleSet = sampleSet;
|
SampleSet = sampleSet;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate playDelegate;
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, SessionStatics statics)
|
||||||
|
{
|
||||||
|
lastPlaybackTime = statics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime);
|
||||||
|
|
||||||
|
sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
playDelegate?.Cancel();
|
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime;
|
||||||
|
|
||||||
if (HoverDebounceTime <= 0)
|
if (enoughTimePassedSinceLastPlayback)
|
||||||
|
{
|
||||||
sampleHover?.Play();
|
sampleHover?.Play();
|
||||||
else
|
lastPlaybackTime.Value = Time.Current;
|
||||||
playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), HoverDebounceTime);
|
}
|
||||||
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(AudioManager audio)
|
|
||||||
{
|
|
||||||
sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HoverSampleSet
|
public enum HoverSampleSet
|
||||||
|
@ -65,6 +65,23 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
|
||||||
|
/// </summary>
|
||||||
|
public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the <see cref="LocalUser"/> is the host in <see cref="Room"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHost
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var localUser = LocalUser;
|
||||||
|
return localUser != null && Room?.Host != null && localUser.Equals(Room.Host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache userLookupCache { get; set; } = null!;
|
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||||
|
|
||||||
@ -178,6 +195,32 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the <see cref="LocalUser"/>'s ready state.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">If a toggle of ready state is not valid at this time.</exception>
|
||||||
|
public async Task ToggleReady()
|
||||||
|
{
|
||||||
|
var localUser = LocalUser;
|
||||||
|
|
||||||
|
if (localUser == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (localUser.State)
|
||||||
|
{
|
||||||
|
case MultiplayerUserState.Idle:
|
||||||
|
await ChangeState(MultiplayerUserState.Ready);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Ready:
|
||||||
|
await ChangeState(MultiplayerUserState.Idle);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Task TransferHost(int userId);
|
public abstract Task TransferHost(int userId);
|
||||||
|
|
||||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||||
|
@ -52,9 +52,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (newIndex == selectedIndex) return false;
|
if (newIndex == selectedIndex) return false;
|
||||||
|
|
||||||
int direction = newIndex < selectedIndex ? -1 : 1;
|
int direction = newIndex < selectedIndex ? -1 : 1;
|
||||||
|
|
||||||
bool beforeSelected = Selected;
|
bool beforeSelected = Selected;
|
||||||
|
|
||||||
Mod modBefore = SelectedMod ?? Mods[0];
|
Mod previousSelection = SelectedMod ?? Mods[0];
|
||||||
|
|
||||||
if (newIndex >= Mods.Length)
|
if (newIndex >= Mods.Length)
|
||||||
newIndex = -1;
|
newIndex = -1;
|
||||||
@ -65,40 +66,45 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
selectedIndex = newIndex;
|
selectedIndex = newIndex;
|
||||||
Mod modAfter = SelectedMod ?? Mods[0];
|
|
||||||
|
|
||||||
if (beforeSelected != Selected)
|
Mod newSelection = SelectedMod ?? Mods[0];
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
|
if (beforeSelected != Selected)
|
||||||
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modBefore != modAfter)
|
|
||||||
{
|
|
||||||
const float rotate_angle = 16;
|
|
||||||
|
|
||||||
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
|
||||||
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
|
||||||
|
|
||||||
backgroundIcon.Mod = modAfter;
|
|
||||||
|
|
||||||
using (BeginDelayedSequence(mod_switch_duration, true))
|
|
||||||
{
|
{
|
||||||
foregroundIcon
|
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
|
||||||
.RotateTo(-rotate_angle * direction)
|
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
|
||||||
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
|
||||||
|
|
||||||
backgroundIcon
|
|
||||||
.RotateTo(rotate_angle * direction)
|
|
||||||
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
|
||||||
|
|
||||||
Schedule(() => displayMod(modAfter));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foregroundIcon.Selected.Value = Selected;
|
if (previousSelection != newSelection)
|
||||||
|
{
|
||||||
|
const float rotate_angle = 16;
|
||||||
|
|
||||||
|
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
||||||
|
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
backgroundIcon.Mod = newSelection;
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(mod_switch_duration, true))
|
||||||
|
{
|
||||||
|
foregroundIcon
|
||||||
|
.RotateTo(-rotate_angle * direction)
|
||||||
|
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
backgroundIcon
|
||||||
|
.RotateTo(rotate_angle * direction)
|
||||||
|
.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
Schedule(() => displayMod(newSelection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foregroundIcon.Selected.Value = Selected;
|
||||||
|
});
|
||||||
|
|
||||||
SelectionChanged?.Invoke(SelectedMod);
|
SelectionChanged?.Invoke(SelectedMod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,20 +127,30 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Select one or more mods in this section and deselects all other ones.
|
/// Updates all buttons with the given list of selected mods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be selected.</param>
|
/// <param name="newSelectedMods">The new list of selected mods to select.</param>
|
||||||
public void SelectTypes(IEnumerable<Type> modTypes)
|
public void UpdateSelectedMods(IReadOnlyList<Mod> newSelectedMods)
|
||||||
{
|
{
|
||||||
foreach (var button in buttons)
|
foreach (var button in buttons)
|
||||||
{
|
updateButtonMods(button, newSelectedMods);
|
||||||
int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t == m.GetType()));
|
}
|
||||||
|
|
||||||
if (i >= 0)
|
private void updateButtonMods(ModButton button, IReadOnlyList<Mod> newSelectedMods)
|
||||||
button.SelectAt(i);
|
{
|
||||||
else
|
foreach (var mod in newSelectedMods)
|
||||||
button.Deselect();
|
{
|
||||||
|
var index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType());
|
||||||
|
if (index < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var buttonMod = button.Mods[index];
|
||||||
|
buttonMod.CopyFrom(mod);
|
||||||
|
button.SelectAt(index);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.Deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ModSection()
|
protected ModSection()
|
||||||
|
@ -249,7 +249,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Width = 180,
|
Width = 180,
|
||||||
Text = "Deselect All",
|
Text = "Deselect All",
|
||||||
Action = DeselectAll,
|
Action = deselectAll,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
@ -318,7 +318,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeselectAll()
|
private void deselectAll()
|
||||||
{
|
{
|
||||||
foreach (var section in ModSectionsContainer.Children)
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
section.DeselectAll();
|
section.DeselectAll();
|
||||||
@ -331,7 +331,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
|
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
|
||||||
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
|
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
|
||||||
public void DeselectTypes(Type[] modTypes, bool immediate = false)
|
private void deselectTypes(Type[] modTypes, bool immediate = false)
|
||||||
{
|
{
|
||||||
if (modTypes.Length == 0) return;
|
if (modTypes.Length == 0) return;
|
||||||
|
|
||||||
@ -409,7 +409,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
{
|
{
|
||||||
foreach (var section in ModSectionsContainer.Children)
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList());
|
section.UpdateSelectedMods(mods.NewValue);
|
||||||
|
|
||||||
updateMods();
|
updateMods();
|
||||||
}
|
}
|
||||||
@ -438,7 +438,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
if (State.Value == Visibility.Visible) sampleOn?.Play();
|
if (State.Value == Visibility.Visible) sampleOn?.Play();
|
||||||
|
|
||||||
DeselectTypes(selectedMod.IncompatibleMods, true);
|
deselectTypes(selectedMod.IncompatibleMods, true);
|
||||||
|
|
||||||
if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show();
|
if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show();
|
||||||
}
|
}
|
||||||
|
@ -128,20 +128,29 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Mod CreateCopy()
|
public virtual Mod CreateCopy()
|
||||||
{
|
{
|
||||||
var copy = (Mod)Activator.CreateInstance(GetType());
|
var result = (Mod)Activator.CreateInstance(GetType());
|
||||||
|
result.CopyFrom(this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies mod setting values from <paramref name="source"/> into this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The mod to copy properties from.</param>
|
||||||
|
public void CopyFrom(Mod source)
|
||||||
|
{
|
||||||
|
if (source.GetType() != GetType())
|
||||||
|
throw new ArgumentException($"Expected mod of type {GetType()}, got {source.GetType()}.", nameof(source));
|
||||||
|
|
||||||
// Copy bindable values across
|
|
||||||
foreach (var (_, prop) in this.GetSettingsSourceProperties())
|
foreach (var (_, prop) in this.GetSettingsSourceProperties())
|
||||||
{
|
{
|
||||||
var origBindable = (IBindable)prop.GetValue(this);
|
var targetBindable = (IBindable)prop.GetValue(this);
|
||||||
var copyBindable = (IBindable)prop.GetValue(copy);
|
var sourceBindable = (IBindable)prop.GetValue(source);
|
||||||
|
|
||||||
// we only care about changes that have been made away from defaults.
|
// we only care about changes that have been made away from defaults.
|
||||||
if (!origBindable.IsDefault)
|
if (!sourceBindable.IsDefault)
|
||||||
copy.CopyAdjustedSetting(copyBindable, origBindable);
|
CopyAdjustedSetting(targetBindable, sourceBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -116,18 +116,30 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
internal override void CopyAdjustedSetting(IBindable target, object source)
|
internal override void CopyAdjustedSetting(IBindable target, object source)
|
||||||
{
|
{
|
||||||
userChangedSettings[target] = true;
|
// if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default.
|
||||||
|
// if the value is bindable, defer to the source's IsDefault to be able to tell.
|
||||||
|
userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault;
|
||||||
base.CopyAdjustedSetting(target, source);
|
base.CopyAdjustedSetting(target, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a setting from a configuration bindable using <paramref name="applyFunc"/>, if it has been changed by the user.
|
||||||
|
/// </summary>
|
||||||
|
protected void ApplySetting<T>(BindableNumber<T> setting, Action<T> applyFunc)
|
||||||
|
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
|
||||||
|
{
|
||||||
|
if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting)
|
||||||
|
applyFunc.Invoke(setting.Value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply all custom settings to the provided beatmap.
|
/// Apply all custom settings to the provided beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
||||||
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
difficulty.DrainRate = DrainRate.Value;
|
ApplySetting(DrainRate, dr => difficulty.DrainRate = dr);
|
||||||
difficulty.OverallDifficulty = OverallDifficulty.Value;
|
ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -750,7 +750,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (Result.Type != originalType)
|
if (Result.Type != originalType)
|
||||||
{
|
{
|
||||||
Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n"
|
Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n"
|
||||||
+ $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2020-03-28 onwards.", level: LogLevel.Important);
|
+ $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2021-03-28 onwards.", level: LogLevel.Important);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy
|
namespace osu.Game.Rulesets.Objects.Legacy
|
||||||
{
|
{
|
||||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset,
|
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset
|
||||||
#pragma warning disable 618
|
|
||||||
IHasCurve
|
|
||||||
#pragma warning restore 618
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||||
|
@ -1,55 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
|
||||||
{
|
|
||||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
|
||||||
public interface IHasCurve : IHasDistance, IHasRepeats
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The curve.
|
|
||||||
/// </summary>
|
|
||||||
SliderPath Path { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning disable 618
|
|
||||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
|
||||||
public static class HasCurveExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the position on the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The curve.</param>
|
|
||||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
|
||||||
/// <returns>The position on the curve.</returns>
|
|
||||||
public static Vector2 CurvePositionAt(this IHasCurve obj, double progress)
|
|
||||||
=> obj.Path.PositionAt(obj.ProgressAt(progress));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The curve.</param>
|
|
||||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
|
||||||
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
|
|
||||||
public static double ProgressAt(this IHasCurve obj, double progress)
|
|
||||||
{
|
|
||||||
double p = progress * obj.SpanCount() % 1;
|
|
||||||
if (obj.SpanAt(progress) % 2 == 1)
|
|
||||||
p = 1 - p;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines which span of the curve the progress point is on.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The curve.</param>
|
|
||||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
|
||||||
/// <returns>[0, SpanCount) where 0 is the first run.</returns>
|
|
||||||
public static int SpanAt(this IHasCurve obj, double progress)
|
|
||||||
=> (int)(progress * obj.SpanCount());
|
|
||||||
}
|
|
||||||
#pragma warning restore 618
|
|
||||||
}
|
|
@ -6,26 +6,16 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A HitObject that ends at a different time than its start time.
|
/// A HitObject that ends at a different time than its start time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#pragma warning disable 618
|
public interface IHasDuration
|
||||||
public interface IHasDuration : IHasEndTime
|
|
||||||
#pragma warning restore 618
|
|
||||||
{
|
{
|
||||||
double IHasEndTime.EndTime
|
|
||||||
{
|
|
||||||
get => EndTime;
|
|
||||||
set => Duration = (Duration - EndTime) + value;
|
|
||||||
}
|
|
||||||
|
|
||||||
double IHasEndTime.Duration => Duration;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the HitObject ends.
|
/// The time at which the HitObject ends.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
new double EndTime { get; }
|
double EndTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duration of the HitObject.
|
/// The duration of the HitObject.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
new double Duration { get; set; }
|
double Duration { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A HitObject that ends at a different time than its start time.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IHasDuration instead.")] // can be removed 20201126
|
|
||||||
public interface IHasEndTime
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The time at which the HitObject ends.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
double EndTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The duration of the HitObject.
|
|
||||||
/// </summary>
|
|
||||||
double Duration { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -100,9 +100,7 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||||
{
|
{
|
||||||
// todo: StartsWith can be changed to Equals on 2020-11-08
|
if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||||
// This is to give users enough time to have their database use new abbreviated info).
|
|
||||||
if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
|
||||||
context.RulesetInfo.Add(r.RulesetInfo);
|
context.RulesetInfo.Add(r.RulesetInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ namespace osu.Game.Screens.Edit.Compose
|
|||||||
{
|
{
|
||||||
public class ComposeScreen : EditorScreenWithTimeline
|
public class ComposeScreen : EditorScreenWithTimeline
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
private HitObjectComposer composer;
|
private HitObjectComposer composer;
|
||||||
|
|
||||||
public ComposeScreen()
|
public ComposeScreen()
|
||||||
@ -59,7 +62,7 @@ namespace osu.Game.Screens.Edit.Compose
|
|||||||
{
|
{
|
||||||
Debug.Assert(ruleset != null);
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin);
|
||||||
|
|
||||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||||
// full access to all skin sources.
|
// full access to all skin sources.
|
||||||
|
@ -486,6 +486,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
||||||
resetTrack();
|
resetTrack();
|
||||||
|
|
||||||
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
// 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.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
{
|
{
|
||||||
@ -14,9 +12,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class EditorScreen : Container
|
public abstract class EditorScreen : Container
|
||||||
{
|
{
|
||||||
[Resolved]
|
|
||||||
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected EditorBeatmap EditorBeatmap { get; private set; }
|
protected EditorBeatmap EditorBeatmap { get; private set; }
|
||||||
|
|
||||||
|
@ -30,16 +30,16 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Container mainContent;
|
||||||
|
|
||||||
|
private LoadingSpinner spinner;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
|
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
|
||||||
{
|
{
|
||||||
if (beatDivisor != null)
|
if (beatDivisor != null)
|
||||||
this.beatDivisor.BindTo(beatDivisor);
|
this.beatDivisor.BindTo(beatDivisor);
|
||||||
|
|
||||||
Container mainContent;
|
|
||||||
|
|
||||||
LoadingSpinner spinner;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
mainContent = new Container
|
mainContent = new Container
|
||||||
@ -99,6 +99,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
LoadComponentAsync(CreateMainContent(), content =>
|
LoadComponentAsync(CreateMainContent(), content =>
|
||||||
{
|
{
|
||||||
|
@ -13,9 +13,6 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
internal class DifficultySection : SetupSection
|
internal class DifficultySection : SetupSection
|
||||||
{
|
{
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap editorBeatmap { get; set; }
|
|
||||||
|
|
||||||
private LabelledSliderBar<float> circleSizeSlider;
|
private LabelledSliderBar<float> circleSizeSlider;
|
||||||
private LabelledSliderBar<float> healthDrainSlider;
|
private LabelledSliderBar<float> healthDrainSlider;
|
||||||
private LabelledSliderBar<float> approachRateSlider;
|
private LabelledSliderBar<float> approachRateSlider;
|
||||||
@ -34,7 +31,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
Label = "Object Size",
|
Label = "Object Size",
|
||||||
Description = "The size of all hit objects",
|
Description = "The size of all hit objects",
|
||||||
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize)
|
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -46,7 +43,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
Label = "Health Drain",
|
Label = "Health Drain",
|
||||||
Description = "The rate of passive health drain throughout playable time",
|
Description = "The rate of passive health drain throughout playable time",
|
||||||
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate)
|
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -58,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
Label = "Approach Rate",
|
Label = "Approach Rate",
|
||||||
Description = "The speed at which objects are presented to the player",
|
Description = "The speed at which objects are presented to the player",
|
||||||
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate)
|
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -70,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
Label = "Overall Difficulty",
|
Label = "Overall Difficulty",
|
||||||
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
|
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
|
||||||
Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty)
|
Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -88,12 +85,12 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
|
Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
|
||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
|
Beatmap.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
|
Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
|
||||||
editorBeatmap.UpdateAllHitObjects();
|
Beatmap.UpdateAllHitObjects();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,25 +29,25 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
artistTextBox = new LabelledTextBox
|
artistTextBox = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Artist",
|
Label = "Artist",
|
||||||
Current = { Value = Beatmap.Value.Metadata.Artist },
|
Current = { Value = Beatmap.Metadata.Artist },
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
titleTextBox = new LabelledTextBox
|
titleTextBox = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Title",
|
Label = "Title",
|
||||||
Current = { Value = Beatmap.Value.Metadata.Title },
|
Current = { Value = Beatmap.Metadata.Title },
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
creatorTextBox = new LabelledTextBox
|
creatorTextBox = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Creator",
|
Label = "Creator",
|
||||||
Current = { Value = Beatmap.Value.Metadata.AuthorString },
|
Current = { Value = Beatmap.Metadata.AuthorString },
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
difficultyTextBox = new LabelledTextBox
|
difficultyTextBox = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Difficulty Name",
|
Label = "Difficulty Name",
|
||||||
Current = { Value = Beatmap.Value.BeatmapInfo.Version },
|
Current = { Value = Beatmap.BeatmapInfo.Version },
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -62,10 +62,10 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
|
Beatmap.Metadata.Artist = artistTextBox.Current.Value;
|
||||||
Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
|
Beatmap.Metadata.Title = titleTextBox.Current.Value;
|
||||||
Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
|
Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
|
||||||
Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,9 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> working { get; set; }
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private Editor editor { get; set; }
|
private Editor editor { get; set; }
|
||||||
|
|
||||||
@ -70,7 +73,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
audioTrackTextBox = new FileChooserLabelledTextBox
|
audioTrackTextBox = new FileChooserLabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Audio Track",
|
Label = "Audio Track",
|
||||||
Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" },
|
Current = { Value = working.Value.Metadata.AudioFile ?? "Click to select a track" },
|
||||||
Target = audioTrackFileChooserContainer,
|
Target = audioTrackFileChooserContainer,
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
@ -115,11 +118,11 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
if (!info.Exists)
|
if (!info.Exists)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var set = Beatmap.Value.BeatmapSetInfo;
|
var set = working.Value.BeatmapSetInfo;
|
||||||
|
|
||||||
// remove the previous background for now.
|
// remove the previous background for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile);
|
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile);
|
||||||
|
|
||||||
using (var stream = info.OpenRead())
|
using (var stream = info.OpenRead())
|
||||||
{
|
{
|
||||||
@ -129,7 +132,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
beatmaps.AddFile(set, stream, info.Name);
|
beatmaps.AddFile(set, stream, info.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Beatmap.Value.Metadata.BackgroundFile = info.Name;
|
working.Value.Metadata.BackgroundFile = info.Name;
|
||||||
updateBackgroundSprite();
|
updateBackgroundSprite();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -148,11 +151,11 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
if (!info.Exists)
|
if (!info.Exists)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var set = Beatmap.Value.BeatmapSetInfo;
|
var set = working.Value.BeatmapSetInfo;
|
||||||
|
|
||||||
// remove the previous audio track for now.
|
// remove the previous audio track for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile);
|
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.AudioFile);
|
||||||
|
|
||||||
using (var stream = info.OpenRead())
|
using (var stream = info.OpenRead())
|
||||||
{
|
{
|
||||||
@ -162,7 +165,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
beatmaps.AddFile(set, stream, info.Name);
|
beatmaps.AddFile(set, stream, info.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Beatmap.Value.Metadata.AudioFile = info.Name;
|
working.Value.Metadata.AudioFile = info.Name;
|
||||||
|
|
||||||
music.ReloadCurrentTrack();
|
music.ReloadCurrentTrack();
|
||||||
|
|
||||||
@ -178,7 +181,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
private void updateBackgroundSprite()
|
private void updateBackgroundSprite()
|
||||||
{
|
{
|
||||||
LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value)
|
LoadComponentAsync(new BeatmapBackgroundSprite(working.Value)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
// 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.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
protected OsuColour Colours { get; private set; }
|
protected OsuColour Colours { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
protected EditorBeatmap Beatmap { get; private set; }
|
||||||
|
|
||||||
protected override Container<Drawable> Content => flow;
|
protected override Container<Drawable> Content => flow;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
protected override DifficultyControlPoint CreatePoint()
|
protected override DifficultyControlPoint CreatePoint()
|
||||||
{
|
{
|
||||||
var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time);
|
var reference = Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
return new DifficultyControlPoint
|
return new DifficultyControlPoint
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
protected override EffectControlPoint CreatePoint()
|
protected override EffectControlPoint CreatePoint()
|
||||||
{
|
{
|
||||||
var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time);
|
var reference = Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
return new EffectControlPoint
|
return new EffectControlPoint
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
@ -24,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
protected EditorBeatmap Beatmap { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock clock { get; set; }
|
private EditorClock clock { get; set; }
|
||||||
@ -107,13 +106,13 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray();
|
var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray();
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
|
Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
|
||||||
|
|
||||||
foreach (var cp in currentGroupItems)
|
foreach (var cp in currentGroupItems)
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp);
|
Beatmap.ControlPointInfo.Add(time, cp);
|
||||||
|
|
||||||
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
||||||
SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true);
|
SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
protected override SampleControlPoint CreatePoint()
|
protected override SampleControlPoint CreatePoint()
|
||||||
{
|
{
|
||||||
var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time);
|
var reference = Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
return new SampleControlPoint
|
return new SampleControlPoint
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
private const float header_height = 20;
|
private const float header_height = 20;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
protected EditorBeatmap Beatmap { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -62,7 +61,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
private EditorClock clock { get; set; }
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
protected EditorBeatmap Beatmap { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||||
@ -124,7 +123,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
|
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
|
||||||
|
|
||||||
controlPointGroups.BindTo(Beatmap.Value.Beatmap.ControlPointInfo.Groups);
|
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||||
controlPointGroups.BindCollectionChanged((sender, args) =>
|
controlPointGroups.BindCollectionChanged((sender, args) =>
|
||||||
{
|
{
|
||||||
table.ControlGroups = controlPointGroups;
|
table.ControlGroups = controlPointGroups;
|
||||||
@ -137,14 +136,14 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
if (selectedGroup.Value == null)
|
if (selectedGroup.Value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||||
|
|
||||||
selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNew()
|
private void addNew()
|
||||||
{
|
{
|
||||||
selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
selectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
protected override TimingControlPoint CreatePoint()
|
protected override TimingControlPoint CreatePoint()
|
||||||
{
|
{
|
||||||
var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time);
|
var reference = Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
return new TimingControlPoint
|
return new TimingControlPoint
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -26,6 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||||
|
|
||||||
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||||
|
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||||
|
|
||||||
private FilterControl filter;
|
private FilterControl filter;
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -37,7 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
private bool joiningRoom;
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable joiningRoomOperation { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -98,7 +106,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
||||||
initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true);
|
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
|
||||||
|
|
||||||
|
if (ongoingOperationTracker != null)
|
||||||
|
{
|
||||||
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
|
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -156,26 +170,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
|
|
||||||
private void joinRequested(Room room)
|
private void joinRequested(Room room)
|
||||||
{
|
{
|
||||||
joiningRoom = true;
|
Debug.Assert(joiningRoomOperation == null);
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
|
||||||
|
|
||||||
RoomManager?.JoinRoom(room, r =>
|
RoomManager?.JoinRoom(room, r =>
|
||||||
{
|
{
|
||||||
Open(room);
|
Open(room);
|
||||||
joiningRoom = false;
|
joiningRoomOperation?.Dispose();
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = null;
|
||||||
}, _ =>
|
}, _ =>
|
||||||
{
|
{
|
||||||
joiningRoom = false;
|
joiningRoomOperation?.Dispose();
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onInitialRoomsReceivedChanged(ValueChangedEvent<bool> received) => updateLoadingLayer();
|
|
||||||
|
|
||||||
private void updateLoadingLayer()
|
private void updateLoadingLayer()
|
||||||
{
|
{
|
||||||
if (joiningRoom || !initialRoomsReceived.Value)
|
if (operationInProgress.Value || !initialRoomsReceived.Value)
|
||||||
loadingLayer.Show();
|
loadingLayer.Show();
|
||||||
else
|
else
|
||||||
loadingLayer.Hide();
|
loadingLayer.Hide();
|
||||||
|
@ -10,14 +10,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
public class CreateMultiplayerMatchButton : PurpleTriangleButton
|
public class CreateMultiplayerMatchButton : PurpleTriangleButton
|
||||||
{
|
{
|
||||||
|
private IBindable<bool> isConnected;
|
||||||
|
private IBindable<bool> operationInProgress;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private StatefulMultiplayerClient multiplayerClient { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(StatefulMultiplayerClient multiplayerClient)
|
private void load()
|
||||||
{
|
{
|
||||||
Triangles.TriangleScale = 1.5f;
|
Triangles.TriangleScale = 1.5f;
|
||||||
|
|
||||||
Text = "Create room";
|
Text = "Create room";
|
||||||
|
|
||||||
((IBindable<bool>)Enabled).BindTo(multiplayerClient.IsConnected);
|
isConnected = multiplayerClient.IsConnected.GetBoundCopy();
|
||||||
|
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
isConnected.BindValueChanged(_ => updateState());
|
||||||
|
operationInProgress.BindValueChanged(_ => updateState(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState() => Enabled.Value = isConnected.Value && !operationInProgress.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -19,7 +20,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||||
|
|
||||||
|
public Action OnReadyClick
|
||||||
|
{
|
||||||
|
set => readyButton.OnReadyClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Drawable background;
|
private readonly Drawable background;
|
||||||
|
private readonly MultiplayerReadyButton readyButton;
|
||||||
|
|
||||||
public MultiplayerMatchFooter()
|
public MultiplayerMatchFooter()
|
||||||
{
|
{
|
||||||
@ -29,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
new MultiplayerReadyButton
|
readyButton = new MultiplayerReadyButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// 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.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -68,6 +70,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
private readonly IBindable<bool> operationInProgress = new BindableBool();
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable applyingSettingsOperation;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -265,13 +275,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||||
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
||||||
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||||
|
|
||||||
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
|
operationInProgress.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
if (v.NewValue)
|
||||||
|
loadingLayer.Show();
|
||||||
|
else
|
||||||
|
loadingLayer.Hide();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0;
|
ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void apply()
|
private void apply()
|
||||||
@ -280,7 +299,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
hideError();
|
hideError();
|
||||||
loadingLayer.Show();
|
|
||||||
|
Debug.Assert(applyingSettingsOperation == null);
|
||||||
|
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
// If the client is already in a room, update via the client.
|
// If the client is already in a room, update via the client.
|
||||||
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
||||||
@ -313,16 +334,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void onSuccess(Room room)
|
private void onSuccess(Room room)
|
||||||
{
|
{
|
||||||
loadingLayer.Hide();
|
Debug.Assert(applyingSettingsOperation != null);
|
||||||
|
|
||||||
SettingsApplied?.Invoke();
|
SettingsApplied?.Invoke();
|
||||||
|
|
||||||
|
applyingSettingsOperation.Dispose();
|
||||||
|
applyingSettingsOperation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError(string text)
|
private void onError(string text)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(applyingSettingsOperation != null);
|
||||||
|
|
||||||
ErrorText.Text = text;
|
ErrorText.Text = text;
|
||||||
ErrorText.FadeIn(50);
|
ErrorText.FadeIn(50);
|
||||||
|
|
||||||
loadingLayer.Hide();
|
applyingSettingsOperation.Dispose();
|
||||||
|
applyingSettingsOperation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.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;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -24,15 +23,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
public Bindable<PlaylistItem> SelectedItem => button.SelectedItem;
|
public Bindable<PlaylistItem> SelectedItem => button.SelectedItem;
|
||||||
|
|
||||||
|
public Action OnReadyClick
|
||||||
|
{
|
||||||
|
set => button.Action = value;
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
private MultiplayerRoomUser localUser;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
private IBindable<bool> operationInProgress;
|
||||||
|
|
||||||
private SampleChannel sampleReadyCount;
|
private SampleChannel sampleReadyCount;
|
||||||
|
|
||||||
private readonly ButtonWithTrianglesExposed button;
|
private readonly ButtonWithTrianglesExposed button;
|
||||||
@ -46,7 +52,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = Vector2.One,
|
Size = Vector2.One,
|
||||||
Enabled = { Value = true },
|
Enabled = { Value = true },
|
||||||
Action = onClick
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,21 +59,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
|
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
|
||||||
|
|
||||||
|
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
|
||||||
|
operationInProgress.BindValueChanged(_ => updateState());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRoomUpdated()
|
protected override void OnRoomUpdated()
|
||||||
{
|
{
|
||||||
base.OnRoomUpdated();
|
base.OnRoomUpdated();
|
||||||
|
|
||||||
// this method is called on leaving the room, so the local user may not exist in the room any more.
|
|
||||||
localUser = Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
|
|
||||||
|
|
||||||
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open;
|
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
|
var localUser = Client.LocalUser;
|
||||||
|
|
||||||
if (localUser == null)
|
if (localUser == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -100,6 +106,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||||
|
|
||||||
if (newCountReady != countReady)
|
if (newCountReady != countReady)
|
||||||
{
|
{
|
||||||
countReady = newCountReady;
|
countReady = newCountReady;
|
||||||
@ -132,22 +140,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClick()
|
|
||||||
{
|
|
||||||
if (localUser == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (localUser.State == MultiplayerUserState.Idle)
|
|
||||||
Client.ChangeState(MultiplayerUserState.Ready).CatchUnobservedExceptions(true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Room?.Host?.Equals(localUser) == true)
|
|
||||||
Client.StartMatch().CatchUnobservedExceptions(true);
|
|
||||||
else
|
|
||||||
Client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ButtonWithTrianglesExposed : ReadyButton
|
private class ButtonWithTrianglesExposed : ReadyButton
|
||||||
{
|
{
|
||||||
public new Triangles Triangles => base.Triangles;
|
public new Triangles Triangles => base.Triangles;
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
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.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
@ -31,10 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private StatefulMultiplayerClient client { get; set; }
|
private StatefulMultiplayerClient client { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||||
|
|
||||||
private IBindable<bool> isConnected;
|
private IBindable<bool> isConnected;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable readyClickOperation;
|
||||||
|
|
||||||
public MultiplayerMatchSubScreen(Room room)
|
public MultiplayerMatchSubScreen(Room room)
|
||||||
{
|
{
|
||||||
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
|
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
|
||||||
@ -150,7 +159,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new MultiplayerMatchFooter { SelectedItem = { BindTarget = SelectedItem } }
|
new MultiplayerMatchFooter
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
|
OnReadyClick = onReadyClick
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
@ -196,6 +209,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
|
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
|
||||||
|
|
||||||
|
private void onReadyClick()
|
||||||
|
{
|
||||||
|
Debug.Assert(readyClickOperation == null);
|
||||||
|
readyClickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
if (client.IsHost && client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
{
|
||||||
|
client.StartMatch()
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
// accessing Exception here silences any potential errors from the antecedent task
|
||||||
|
if (t.Exception != null)
|
||||||
|
{
|
||||||
|
t.CatchUnobservedExceptions(true); // will run immediately.
|
||||||
|
// gameplay was not started due to an exception; unblock button.
|
||||||
|
endOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// gameplay is starting, the button will be unblocked on load requested.
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.ToggleReady()
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
t.CatchUnobservedExceptions(true); // will run immediately.
|
||||||
|
endOperation();
|
||||||
|
});
|
||||||
|
|
||||||
|
void endOperation()
|
||||||
|
{
|
||||||
|
Debug.Assert(readyClickOperation != null);
|
||||||
|
readyClickOperation.Dispose();
|
||||||
|
readyClickOperation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onLoadRequested()
|
private void onLoadRequested()
|
||||||
{
|
{
|
||||||
Debug.Assert(client.Room != null);
|
Debug.Assert(client.Room != null);
|
||||||
@ -203,6 +254,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
||||||
|
|
||||||
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
||||||
|
|
||||||
|
Debug.Assert(readyClickOperation != null);
|
||||||
|
|
||||||
|
readyClickOperation.Dispose();
|
||||||
|
readyClickOperation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
59
osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
Normal file
59
osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class to track ongoing online operations' progress.
|
||||||
|
/// Can be used to disable interactivity while waiting for a response from online sources.
|
||||||
|
/// </summary>
|
||||||
|
public class OngoingOperationTracker : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether there is an online operation in progress.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> InProgress => inProgress;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> inProgress = new BindableBool();
|
||||||
|
|
||||||
|
private LeasedBindable<bool> leasedInProgress;
|
||||||
|
|
||||||
|
public OngoingOperationTracker()
|
||||||
|
{
|
||||||
|
AlwaysPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins tracking a new online operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IDisposable"/> that will automatically mark the operation as ended on disposal.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="InvalidOperationException">An operation has already been started.</exception>
|
||||||
|
public IDisposable BeginOperation()
|
||||||
|
{
|
||||||
|
if (leasedInProgress != null)
|
||||||
|
throw new InvalidOperationException("Cannot begin operation while another is in progress.");
|
||||||
|
|
||||||
|
leasedInProgress = inProgress.BeginLease(true);
|
||||||
|
leasedInProgress.Value = true;
|
||||||
|
|
||||||
|
// for extra safety, marshal the end of operation back to the update thread if necessary.
|
||||||
|
return new InvokeOnDisposal(() => Scheduler.Add(endOperation, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endOperation()
|
||||||
|
{
|
||||||
|
if (leasedInProgress == null)
|
||||||
|
throw new InvalidOperationException("Cannot end operation multiple times.");
|
||||||
|
|
||||||
|
leasedInProgress.Return();
|
||||||
|
leasedInProgress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,9 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<FilterCriteria> currentFilter = new Bindable<FilterCriteria>(new FilterCriteria());
|
private readonly Bindable<FilterCriteria> currentFilter = new Bindable<FilterCriteria>(new FilterCriteria());
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
@ -141,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
};
|
};
|
||||||
button.Action = () => OpenNewRoom();
|
button.Action = () => OpenNewRoom();
|
||||||
}),
|
}),
|
||||||
RoomManager = CreateRoomManager()
|
RoomManager = CreateRoomManager(),
|
||||||
|
ongoingOperationTracker = new OngoingOperationTracker()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -143,7 +143,11 @@ namespace osu.Game.Screens
|
|||||||
private void load(OsuGame osu, AudioManager audio)
|
private void load(OsuGame osu, AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleExit = audio.Samples.Get(@"UI/screen-back");
|
sampleExit = audio.Samples.Get(@"UI/screen-back");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
Activity.Value ??= InitialActivity;
|
Activity.Value ??= InitialActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -936,7 +936,6 @@ namespace osu.Game.Screens.Select
|
|||||||
Masking = false;
|
Masking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
|
|
||||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
||||||
{
|
{
|
||||||
UserScrolling = true;
|
UserScrolling = true;
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||||
|
|
||||||
|
private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||||
|
|
||||||
public override void OnResuming(IScreen last)
|
public override void OnResuming(IScreen last)
|
||||||
{
|
{
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
@ -50,10 +53,10 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (removeAutoModOnResume)
|
if (removeAutoModOnResume)
|
||||||
{
|
{
|
||||||
var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType();
|
var autoType = getAutoplayMod()?.GetType();
|
||||||
|
|
||||||
if (autoType != null)
|
if (autoType != null)
|
||||||
ModSelect.DeselectTypes(new[] { autoType }, true);
|
Mods.Value = Mods.Value.Where(m => m.GetType() != autoType).ToArray();
|
||||||
|
|
||||||
removeAutoModOnResume = false;
|
removeAutoModOnResume = false;
|
||||||
}
|
}
|
||||||
@ -81,12 +84,9 @@ namespace osu.Game.Screens.Select
|
|||||||
// Ctrl+Enter should start map with autoplay enabled.
|
// Ctrl+Enter should start map with autoplay enabled.
|
||||||
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
|
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
|
||||||
{
|
{
|
||||||
var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
|
var autoplayMod = getAutoplayMod();
|
||||||
var autoType = auto?.GetType();
|
|
||||||
|
|
||||||
var mods = Mods.Value;
|
if (autoplayMod == null)
|
||||||
|
|
||||||
if (autoType == null)
|
|
||||||
{
|
{
|
||||||
notifications?.Post(new SimpleNotification
|
notifications?.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
@ -95,9 +95,11 @@ namespace osu.Game.Screens.Select
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mods.All(m => m.GetType() != autoType))
|
var mods = Mods.Value;
|
||||||
|
|
||||||
|
if (mods.All(m => m.GetType() != autoplayMod.GetType()))
|
||||||
{
|
{
|
||||||
Mods.Value = mods.Append(auto).ToArray();
|
Mods.Value = mods.Append(autoplayMod).ToArray();
|
||||||
removeAutoModOnResume = true;
|
removeAutoModOnResume = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Cached]
|
[Cached]
|
||||||
public Bindable<FilterCriteria> Filter { get; }
|
public Bindable<FilterCriteria> Filter { get; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public OngoingOperationTracker OngoingOperationTracker { get; }
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private readonly TestMultiplayerRoomContainer content;
|
private readonly TestMultiplayerRoomContainer content;
|
||||||
|
|
||||||
@ -36,6 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Client = content.Client;
|
Client = content.Client;
|
||||||
RoomManager = content.RoomManager;
|
RoomManager = content.RoomManager;
|
||||||
Filter = content.Filter;
|
Filter = content.Filter;
|
||||||
|
OngoingOperationTracker = content.OngoingOperationTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
@ -25,6 +25,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Cached]
|
[Cached]
|
||||||
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(new FilterCriteria());
|
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(new FilterCriteria());
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public readonly OngoingOperationTracker OngoingOperationTracker;
|
||||||
|
|
||||||
public TestMultiplayerRoomContainer()
|
public TestMultiplayerRoomContainer()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -33,6 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
Client = new TestMultiplayerClient(),
|
Client = new TestMultiplayerClient(),
|
||||||
RoomManager = new TestMultiplayerRoomManager(),
|
RoomManager = new TestMultiplayerRoomManager(),
|
||||||
|
OngoingOperationTracker = new OngoingOperationTracker(),
|
||||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.1229.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.106.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.8" />
|
<PackageReference Include="Sentry" Version="2.1.8" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
|
@ -70,7 +70,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="2020.1229.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.106.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -88,7 +88,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.1229.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.106.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user