mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 03:33:22 +08:00
Merge pull request #16722 from smoogipoo/spectator-consistency-frames
Implement spectator consistency frames
This commit is contained in:
commit
2c0c44a950
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
|
||||
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
|
||||
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
|
||||
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new ReplayState<EmptyScrollingAction>
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
|
||||
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new ReplayState<PippidonAction>
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() });
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new ReplayState<TaikoAction> { PressedActions = CurrentFrame?.Actions ?? new List<TaikoAction>() });
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@ -42,6 +48,43 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResetFromReplayFrame()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitCircle() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
|
||||
// No header shouldn't cause any change
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
|
||||
// Reset with a miss instead.
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
||||
{
|
||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
||||
});
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
|
||||
// Reset with no judged hit.
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
||||
{
|
||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
||||
});
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult { get; }
|
||||
|
@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
|
||||
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
|
||||
|
@ -218,6 +218,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFinalFrameInBundleHasHeader()
|
||||
{
|
||||
FrameDataBundle lastBundle = null;
|
||||
|
||||
AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle);
|
||||
|
||||
start(-1234);
|
||||
sendFrames();
|
||||
finish();
|
||||
|
||||
AddUntilStep("bundle received", () => lastBundle != null);
|
||||
AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null);
|
||||
AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null);
|
||||
}
|
||||
|
||||
private OsuFramedReplayInputHandler replayHandler =>
|
||||
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
|
||||
|
||||
|
@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
|
||||
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Input.StateChanges.Events;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
@ -79,5 +80,38 @@ namespace osu.Game.Input.Handlers
|
||||
PressedActions = pressedActions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IInput"/> that is triggered when a frame containing replay statistics arrives.
|
||||
/// </summary>
|
||||
public class ReplayStatisticsFrameInput : IInput
|
||||
{
|
||||
/// <summary>
|
||||
/// The frame containing the statistics.
|
||||
/// </summary>
|
||||
public ReplayFrame Frame;
|
||||
|
||||
public void Apply(InputState state, IInputStateChangeHandler handler)
|
||||
{
|
||||
handler.HandleInputStateChange(new ReplayStatisticsFrameEvent(state, this, Frame));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="InputStateChangeEvent"/> that is triggered when a frame containing replay statistics arrives.
|
||||
/// </summary>
|
||||
public class ReplayStatisticsFrameEvent : InputStateChangeEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The frame containing the statistics.
|
||||
/// </summary>
|
||||
public readonly ReplayFrame Frame;
|
||||
|
||||
public ReplayStatisticsFrameEvent(InputState state, IInput input, ReplayFrame frame)
|
||||
: base(state, input)
|
||||
{
|
||||
Frame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +129,9 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data)
|
||||
{
|
||||
if (data.Frames.Count > 0)
|
||||
data.Frames[^1].Header = data.Header;
|
||||
|
||||
Schedule(() => OnNewFrames?.Invoke(userId, data));
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Replays;
|
||||
|
||||
@ -174,5 +175,19 @@ namespace osu.Game.Rulesets.Replays
|
||||
|
||||
return Frames[index].Time;
|
||||
}
|
||||
|
||||
public sealed override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
base.CollectPendingInputs(inputs);
|
||||
|
||||
CollectReplayInputs(inputs);
|
||||
|
||||
if (CurrentFrame?.Header != null)
|
||||
inputs.Add(new ReplayStatisticsFrameInput { Frame = CurrentFrame });
|
||||
}
|
||||
|
||||
protected virtual void CollectReplayInputs(List<IInput> inputs)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,29 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using MessagePack;
|
||||
using osu.Game.Online.Spectator;
|
||||
|
||||
namespace osu.Game.Rulesets.Replays
|
||||
{
|
||||
[MessagePackObject]
|
||||
public class ReplayFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which this <see cref="ReplayFrame"/> takes place.
|
||||
/// </summary>
|
||||
[Key(0)]
|
||||
public double Time;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="FrameHeader"/> containing the state of a play after this <see cref="ReplayFrame"/> takes place.
|
||||
/// May be omitted where exact per-frame accuracy is not required.
|
||||
/// </summary>
|
||||
[IgnoreMember]
|
||||
public FrameHeader? Header;
|
||||
|
||||
public ReplayFrame()
|
||||
{
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// An array of all scorable <see cref="HitResult"/>s.
|
||||
/// </summary>
|
||||
public static readonly HitResult[] SCORABLE_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Where(r => r.IsScorable()).ToArray();
|
||||
public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> is valid within a given <see cref="HitResult"/> range.
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
@ -107,6 +108,25 @@ namespace osu.Game.Rulesets.Scoring
|
||||
JudgedHits = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset all statistics based on header information contained within a replay frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the provided replay frame does not have any header information, this will be a noop.
|
||||
/// </remarks>
|
||||
/// <param name="ruleset">The ruleset to be used for retrieving statistics.</param>
|
||||
/// <param name="frame">The replay frame to read header statistics from.</param>
|
||||
public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame)
|
||||
{
|
||||
if (frame.Header == null)
|
||||
return;
|
||||
|
||||
JudgedHits = 0;
|
||||
|
||||
foreach ((_, int count) in frame.Header.Statistics)
|
||||
JudgedHits += count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
|
@ -7,9 +7,11 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
@ -18,6 +20,11 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
private const double max_score = 1000000;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this <see cref="ScoreProcessor"/> was reset from a replay frame.
|
||||
/// </summary>
|
||||
public event Action OnResetFromReplayFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The current total score.
|
||||
/// </summary>
|
||||
@ -125,6 +132,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (result.FailedAtJudgement)
|
||||
return;
|
||||
|
||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
||||
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
|
||||
@ -151,8 +160,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
rollingMaxBaseScore += result.Judgement.MaxNumericResult;
|
||||
}
|
||||
|
||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
||||
|
||||
hitEvents.Add(CreateHitEvent(result));
|
||||
lastHitObject = result.HitObject;
|
||||
|
||||
@ -175,6 +182,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (result.FailedAtJudgement)
|
||||
return;
|
||||
|
||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
||||
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
|
||||
@ -186,8 +195,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
|
||||
}
|
||||
|
||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
||||
|
||||
Debug.Assert(hitEvents.Count > 0);
|
||||
lastHitObject = hitEvents[^1].LastHitObject;
|
||||
hitEvents.RemoveAt(hitEvents.Count - 1);
|
||||
@ -329,12 +336,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
HighestCombo.Value = 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
hitEvents.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a score populated with data for the current play this processor is responsible for.
|
||||
/// </summary>
|
||||
@ -346,11 +347,72 @@ namespace osu.Game.Rulesets.Scoring
|
||||
score.Accuracy = Accuracy.Value;
|
||||
score.Rank = Rank.Value;
|
||||
|
||||
foreach (var result in HitResultExtensions.SCORABLE_TYPES)
|
||||
foreach (var result in HitResultExtensions.ALL_TYPES)
|
||||
score.Statistics[result] = GetStatistic(result);
|
||||
|
||||
score.HitEvents = hitEvents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum <see cref="HitResult"/> for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via <see cref="ResetFromReplayFrame"/>.
|
||||
/// </summary>
|
||||
private HitResult? maxNormalResult;
|
||||
|
||||
public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame)
|
||||
{
|
||||
base.ResetFromReplayFrame(ruleset, frame);
|
||||
|
||||
if (frame.Header == null)
|
||||
return;
|
||||
|
||||
baseScore = 0;
|
||||
rollingMaxBaseScore = 0;
|
||||
HighestCombo.Value = frame.Header.MaxCombo;
|
||||
|
||||
foreach ((HitResult result, int count) in frame.Header.Statistics)
|
||||
{
|
||||
// Bonus scores are counted separately directly from the statistics dictionary later on.
|
||||
if (!result.IsScorable() || result.IsBonus())
|
||||
continue;
|
||||
|
||||
// The maximum result of this judgement if it wasn't a miss.
|
||||
// E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT).
|
||||
HitResult maxResult;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.LargeTickMiss:
|
||||
maxResult = HitResult.LargeTickHit;
|
||||
break;
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
case HitResult.SmallTickMiss:
|
||||
maxResult = HitResult.SmallTickHit;
|
||||
break;
|
||||
|
||||
default:
|
||||
maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result;
|
||||
break;
|
||||
}
|
||||
|
||||
baseScore += count * Judgement.ToNumericResult(result);
|
||||
rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult);
|
||||
}
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
scoreResultCounts.AddRange(frame.Header.Statistics);
|
||||
|
||||
updateScore();
|
||||
|
||||
OnResetFromReplayFrame?.Invoke();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
hitEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScoringMode
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||
|
||||
@ -26,6 +27,11 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||
|
||||
private readonly Ruleset ruleset;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ScoreProcessor scoreProcessor { get; set; }
|
||||
|
||||
private ReplayRecorder recorder;
|
||||
|
||||
public ReplayRecorder Recorder
|
||||
@ -51,6 +57,8 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
{
|
||||
this.ruleset = ruleset.CreateInstance();
|
||||
|
||||
InternalChild = KeyBindingContainer =
|
||||
CreateKeyBindingContainer(ruleset, variant, unique)
|
||||
.WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
|
||||
@ -66,17 +74,23 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public override void HandleInputStateChange(InputStateChangeEvent inputStateChange)
|
||||
{
|
||||
if (inputStateChange is ReplayStateChangeEvent<T> replayStateChanged)
|
||||
switch (inputStateChange)
|
||||
{
|
||||
foreach (var action in replayStateChanged.ReleasedActions)
|
||||
KeyBindingContainer.TriggerReleased(action);
|
||||
case ReplayStateChangeEvent<T> stateChangeEvent:
|
||||
foreach (var action in stateChangeEvent.ReleasedActions)
|
||||
KeyBindingContainer.TriggerReleased(action);
|
||||
|
||||
foreach (var action in replayStateChanged.PressedActions)
|
||||
KeyBindingContainer.TriggerPressed(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.HandleInputStateChange(inputStateChange);
|
||||
foreach (var action in stateChangeEvent.PressedActions)
|
||||
KeyBindingContainer.TriggerPressed(action);
|
||||
break;
|
||||
|
||||
case ReplayStatisticsFrameEvent statisticsStateChangeEvent:
|
||||
scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame);
|
||||
break;
|
||||
|
||||
default:
|
||||
base.HandleInputStateChange(inputStateChange);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,6 +170,7 @@ namespace osu.Game.Screens.Play
|
||||
PrepareReplay();
|
||||
|
||||
ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo);
|
||||
ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo);
|
||||
|
||||
gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
var convertedFrame = (ReplayFrame)convertibleFrame;
|
||||
convertedFrame.Time = frame.Time;
|
||||
convertedFrame.Header = frame.Header;
|
||||
|
||||
score.Replay.Frames.Add(convertedFrame);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user