1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-03 21:22:57 +08:00

Merge branch 'master' into spectator-consistency-frames

This commit is contained in:
Dan Balasescu 2022-02-01 14:35:30 +09:00
commit 0641264a11
13 changed files with 222 additions and 64 deletions

1
.gitignore vendored
View File

@ -339,3 +339,4 @@ inspectcode
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
**/FodyWeavers.xml

View File

@ -1,53 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.7.0)
CFPropertyList (3.0.5)
rexml
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.413.0)
aws-sdk-core (3.110.0)
aws-eventstream (1.2.0)
aws-partitions (1.551.0)
aws-sdk-core (3.125.5)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.40.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (1.53.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.87.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-s3 (1.111.3)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.3)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.1)
excon (0.78.1)
faraday (1.2.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
emoji_regex (3.2.3)
excon (0.90.0)
faraday (1.9.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.1)
fastlane (2.170.0)
fastimage (2.2.6)
fastlane (2.181.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
@ -68,6 +90,7 @@ GEM
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
@ -94,65 +117,80 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.29.2)
addressable (~> 2.5)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (0.14.0)
googleauth (0.17.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
signet (~> 0.15)
highline (1.7.10)
http-cookie (1.0.3)
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.2)
jmespath (1.5.0)
json (2.6.1)
jwt (2.3.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.0.2)
mini_mime (1.1.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.0)
naturally (2.2.1)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
os (1.1.1)
plist (3.5.0)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.3)
representable (3.0.4)
rake (13.0.6)
representable (3.1.1)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.2)
rubyzip (2.3.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
signet (0.16.0)
addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
slack-notifier (2.4.0)
souyuz (0.9.1)
fastlane (>= 1.103.0)
highline (~> 1.7)
@ -160,6 +198,7 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@ -167,18 +206,20 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
unf_ext (0.0.8)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS

View File

@ -12,7 +12,7 @@ Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
or alternatively using `brew install fastlane`
# Available Actions
## Android

View File

@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania
{
[Cached] // Used for touch input, see ColumnTouchInputArea.
public class ManiaInputManager : RulesetInputManager<ManiaAction>
{
public ManiaInputManager(RulesetInfo ruleset, int variant)

View File

@ -68,7 +68,8 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
new ColumnTouchInputArea(this)
};
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
@ -139,5 +140,50 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
public class ColumnTouchInputArea : Drawable
{
private readonly Column column;
[Resolved(canBeNull: true)]
private ManiaInputManager maniaInputManager { get; set; }
private KeyBindingContainer<ManiaAction> keyBindingContainer;
public ColumnTouchInputArea(Column column)
{
RelativeSizeAxes = Axes.Both;
this.column = column;
}
protected override void LoadComplete()
{
keyBindingContainer = maniaInputManager?.KeyBindingContainer;
}
protected override bool OnMouseDown(MouseDownEvent e)
{
keyBindingContainer?.TriggerPressed(column.Action.Value);
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
keyBindingContainer?.TriggerReleased(column.Action.Value);
base.OnMouseUp(e);
}
protected override bool OnTouchDown(TouchDownEvent e)
{
keyBindingContainer?.TriggerPressed(column.Action.Value);
return true;
}
protected override void OnTouchUp(TouchUpEvent e)
{
keyBindingContainer?.TriggerReleased(column.Action.Value);
}
}
}
}

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
AlwaysPresent = true
}
}
}
},
}
};

View File

@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true)
/// <summary>
/// Tests spectating with a gameplay start time set to a negative value.
/// Simulating beatmaps with high <see cref="BeatmapInfo.AudioLeadIn"/> or negative time storyboard elements.
/// </summary>
[Test]
public void TestNegativeGameplayStartTime()
{
AddStep("load screen", () =>
start(PLAYER_1_ID);
loadSpectateScreen(false, -500);
// to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay().
// (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete)
AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100));
AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded);
AddWaitStep("wait for progression", 3);
assertNotCatchingUp(PLAYER_1_ID);
assertRunning(PLAYER_1_ID);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null)
{
AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () =>
{
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
Ruleset.Value = importedBeatmap.Ruleset;
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime));
});
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
}
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
private void start(int[] userIds, int? beatmapId = null)
{
AddStep("start play", () =>
@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertMuted(int userId, bool muted)
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
private void assertRunning(int userId)
=> AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning);
private void assertNotCatchingUp(int userId)
=> AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp);
private void waitForCatchup(int userId)
=> AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp);
@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
private class TestMultiSpectatorScreen : MultiSpectatorScreen
{
private readonly double? gameplayStartTime;
public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null)
: base(users)
{
this.gameplayStartTime = gameplayStartTime;
}
protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap)
=> new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue);
}
}
}

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.UI
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler
where T : struct
{
public readonly KeyBindingContainer<T> KeyBindingContainer;
private readonly Ruleset ruleset;
[Resolved(CanBeNull = true)]
@ -49,8 +51,6 @@ namespace osu.Game.Rulesets.UI
protected override InputState CreateInitialState() => new RulesetInputManagerInputState<T>(base.CreateInitialState());
protected readonly KeyBindingContainer<T> KeyBindingContainer;
protected override Container<Drawable> Content => content;
private readonly Container content;

View File

@ -55,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public SpectatorGameplayClockContainer([NotNull] IClock sourceClock)
: base(sourceClock)
{
// the container should initially be in a stopped state until the catch-up clock is started by the sync manager.
Stop();
}
protected override void Update()

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
@ -68,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
Container leaderboardContainer;
Container scoreDisplayContainer;
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value);
InternalChildren = new[]
{
@ -235,5 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
return base.OnBackButton();
}
protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0);
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary>
public class PlayerArea : CompositeDrawable
{
/// <summary>
/// Raised after <see cref="Player.StartGameplay"/> is called on <see cref="Player"/>.
/// </summary>
public event Action OnGameplayStarted;
/// <summary>
/// Whether a <see cref="Player"/> is loaded in the area.
/// </summary>
@ -93,7 +98,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
}
};
stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock)));
stack.Push(new MultiSpectatorPlayerLoader(Score, () =>
{
var player = new MultiSpectatorPlayer(Score, GameplayClock);
player.OnGameplayStarted += () => OnGameplayStarted?.Invoke();
return player;
}));
loadingLayer.Hide();
}

View File

@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Stops gameplay.
/// </summary>
public virtual void Stop() => IsPaused.Value = true;
public void Stop() => IsPaused.Value = true;
/// <summary>
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.

View File

@ -45,6 +45,11 @@ namespace osu.Game.Screens.Play
/// </summary>
public const double RESULTS_DISPLAY_DELAY = 1000.0;
/// <summary>
/// Raised after <see cref="StartGameplay"/> is called.
/// </summary>
public event Action OnGameplayStarted;
public override bool AllowBackButton => false; // handled by HoldForMenuButton
protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
@ -959,7 +964,9 @@ namespace osu.Game.Screens.Play
updateGameplayState();
GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint);
StartGameplay();
OnGameplayStarted?.Invoke();
}
/// <summary>