mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 05:32:56 +08:00
Merge remote-tracking branch 'upstream/master' into taiko_drumroll_drawing
This commit is contained in:
commit
041e4f921a
@ -1 +1 @@
|
||||
Subproject commit 3931f8e358365b1853a76e1d141fcf4c929a2143
|
||||
Subproject commit 415884e7e19f9062a4fac457a7ce19b566fa2ee7
|
@ -58,7 +58,6 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Result = HitResult.Hit,
|
||||
TaikoResult = hitResult,
|
||||
TimeOffset = 0,
|
||||
ComboAtHit = 1,
|
||||
SecondHit = RNG.Next(10) == 0
|
||||
}
|
||||
});
|
||||
@ -71,8 +70,7 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Judgement = new TaikoJudgement
|
||||
{
|
||||
Result = HitResult.Miss,
|
||||
TimeOffset = 0,
|
||||
ComboAtHit = 0
|
||||
TimeOffset = 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Modes.Catch.Scoring
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnNewJugement(CatchJudgement judgement)
|
||||
protected override void OnNewJudgement(CatchJudgement judgement)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Modes.Mania.Scoring
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnNewJugement(ManiaJudgement judgement)
|
||||
protected override void OnNewJudgement(ManiaJudgement judgement)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
Colour = AccentColour,
|
||||
Hit = () =>
|
||||
{
|
||||
if (Judgement.Result.HasValue) return false;
|
||||
if (Judgement.Result != HitResult.None) return false;
|
||||
|
||||
Judgement.PositionOffset = Vector2.Zero; //todo: set to correct value
|
||||
UpdateJudgement(true);
|
||||
|
@ -11,10 +11,11 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Modes.Objects.Types;
|
||||
using osu.Game.Modes.Replays;
|
||||
|
||||
namespace osu.Game.Modes.Osu
|
||||
{
|
||||
public class OsuAutoReplay : LegacyReplay
|
||||
public class OsuAutoReplay : Replay
|
||||
{
|
||||
private static readonly Vector2 spinner_centre = new Vector2(256, 192);
|
||||
|
||||
@ -29,17 +30,20 @@ namespace osu.Game.Modes.Osu
|
||||
createAutoReplay();
|
||||
}
|
||||
|
||||
private class LegacyReplayFrameComparer : IComparer<LegacyReplayFrame>
|
||||
private class ReplayFrameComparer : IComparer<ReplayFrame>
|
||||
{
|
||||
public int Compare(LegacyReplayFrame f1, LegacyReplayFrame f2)
|
||||
public int Compare(ReplayFrame f1, ReplayFrame f2)
|
||||
{
|
||||
if (f1 == null) throw new NullReferenceException($@"{nameof(f1)} cannot be null");
|
||||
if (f2 == null) throw new NullReferenceException($@"{nameof(f2)} cannot be null");
|
||||
|
||||
return f1.Time.CompareTo(f2.Time);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IComparer<LegacyReplayFrame> replay_frame_comparer = new LegacyReplayFrameComparer();
|
||||
private static readonly IComparer<ReplayFrame> replay_frame_comparer = new ReplayFrameComparer();
|
||||
|
||||
private int findInsertionIndex(LegacyReplayFrame frame)
|
||||
private int findInsertionIndex(ReplayFrame frame)
|
||||
{
|
||||
int index = Frames.BinarySearch(frame, replay_frame_comparer);
|
||||
|
||||
@ -59,7 +63,7 @@ namespace osu.Game.Modes.Osu
|
||||
return index;
|
||||
}
|
||||
|
||||
private void addFrameToReplay(LegacyReplayFrame frame) => Frames.Insert(findInsertionIndex(frame), frame);
|
||||
private void addFrameToReplay(ReplayFrame frame) => Frames.Insert(findInsertionIndex(frame), frame);
|
||||
|
||||
private static Vector2 circlePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius));
|
||||
|
||||
@ -74,9 +78,9 @@ namespace osu.Game.Modes.Osu
|
||||
|
||||
EasingTypes preferredEasing = DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(-100000, 256, 500, LegacyButtonState.None));
|
||||
addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, LegacyButtonState.None));
|
||||
addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, LegacyButtonState.None));
|
||||
addFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None));
|
||||
addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None));
|
||||
addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None));
|
||||
|
||||
// We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps.
|
||||
float frameDelay = (float)applyModsToRate(1000.0 / 60.0);
|
||||
@ -106,18 +110,18 @@ namespace osu.Game.Modes.Osu
|
||||
//Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
|
||||
if (h.StartTime - h.HitWindowFor(OsuScoreResult.Miss) > endTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Miss), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Miss), h.Position.X, h.Position.Y, ReplayButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50) > endTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50), h.Position.X, h.Position.Y, ReplayButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100) > endTime + h.HitWindowFor(OsuScoreResult.Hit100) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit100), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit100), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100), h.Position.X, h.Position.Y, ReplayButtonState.None));
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,13 +177,13 @@ namespace osu.Game.Modes.Osu
|
||||
// Do some nice easing for cursor movements
|
||||
if (Frames.Count > 0)
|
||||
{
|
||||
LegacyReplayFrame lastFrame = Frames[Frames.Count - 1];
|
||||
ReplayFrame lastFrame = Frames[Frames.Count - 1];
|
||||
|
||||
// Wait until Auto could "see and react" to the next note.
|
||||
double waitTime = h.StartTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime);
|
||||
if (waitTime > lastFrame.Time)
|
||||
{
|
||||
lastFrame = new LegacyReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
|
||||
lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
|
||||
addFrameToReplay(lastFrame);
|
||||
}
|
||||
|
||||
@ -196,7 +200,7 @@ namespace osu.Game.Modes.Osu
|
||||
for (double time = lastFrame.Time + frameDelay; time < h.StartTime; time += frameDelay)
|
||||
{
|
||||
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPosition, lastFrame.Time, h.StartTime, easing);
|
||||
addFrameToReplay(new LegacyReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
|
||||
addFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
|
||||
}
|
||||
|
||||
buttonIndex = 0;
|
||||
@ -207,12 +211,12 @@ namespace osu.Game.Modes.Osu
|
||||
}
|
||||
}
|
||||
|
||||
LegacyButtonState button = buttonIndex % 2 == 0 ? LegacyButtonState.Left1 : LegacyButtonState.Right1;
|
||||
ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1;
|
||||
|
||||
double hEndTime = (h as IHasEndTime)?.EndTime ?? h.StartTime;
|
||||
|
||||
LegacyReplayFrame newFrame = new LegacyReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button);
|
||||
LegacyReplayFrame endFrame = new LegacyReplayFrame(hEndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, LegacyButtonState.None);
|
||||
ReplayFrame newFrame = new ReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button);
|
||||
ReplayFrame endFrame = new ReplayFrame(hEndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, ReplayButtonState.None);
|
||||
|
||||
// Decrement because we want the previous frame, not the next one
|
||||
int index = findInsertionIndex(newFrame) - 1;
|
||||
@ -220,19 +224,19 @@ namespace osu.Game.Modes.Osu
|
||||
// Do we have a previous frame? No need to check for < replay.Count since we decremented!
|
||||
if (index >= 0)
|
||||
{
|
||||
LegacyReplayFrame previousFrame = Frames[index];
|
||||
ReplayFrame previousFrame = Frames[index];
|
||||
var previousButton = previousFrame.ButtonState;
|
||||
|
||||
// If a button is already held, then we simply alternate
|
||||
if (previousButton != LegacyButtonState.None)
|
||||
if (previousButton != ReplayButtonState.None)
|
||||
{
|
||||
Debug.Assert(previousButton != (LegacyButtonState.Left1 | LegacyButtonState.Right1));
|
||||
Debug.Assert(previousButton != (ReplayButtonState.Left1 | ReplayButtonState.Right1));
|
||||
|
||||
// Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
|
||||
if (previousButton == button)
|
||||
{
|
||||
button = (LegacyButtonState.Left1 | LegacyButtonState.Right1) & ~button;
|
||||
newFrame.SetButtonStates(button);
|
||||
button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button;
|
||||
newFrame.ButtonState = button;
|
||||
}
|
||||
|
||||
// We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
|
||||
@ -246,7 +250,7 @@ namespace osu.Game.Modes.Osu
|
||||
{
|
||||
// Don't affect frames which stop pressing a button!
|
||||
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
|
||||
Frames[j].SetButtonStates(button);
|
||||
Frames[j].ButtonState = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -270,13 +274,13 @@ namespace osu.Game.Modes.Osu
|
||||
t = applyModsToTime(j - h.StartTime) * spinnerDirection;
|
||||
|
||||
Vector2 pos = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
|
||||
addFrameToReplay(new LegacyReplayFrame((int)j, pos.X, pos.Y, button));
|
||||
addFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button));
|
||||
}
|
||||
|
||||
t = applyModsToTime(s.EndTime - h.StartTime) * spinnerDirection;
|
||||
Vector2 endPosition = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
|
||||
addFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
|
||||
|
||||
endFrame.MouseX = endPosition.X;
|
||||
endFrame.MouseY = endPosition.Y;
|
||||
@ -288,10 +292,10 @@ namespace osu.Game.Modes.Osu
|
||||
for (double j = frameDelay; j < s.Duration; j += frameDelay)
|
||||
{
|
||||
Vector2 pos = s.PositionAt(j / s.Duration);
|
||||
addFrameToReplay(new LegacyReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
|
||||
addFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
|
||||
}
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button));
|
||||
addFrameToReplay(new ReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button));
|
||||
}
|
||||
|
||||
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Modes.Osu.Scoring
|
||||
Accuracy.Value = 1;
|
||||
}
|
||||
|
||||
protected override void OnNewJugement(OsuJudgement judgement)
|
||||
protected override void OnNewJudgement(OsuJudgement judgement)
|
||||
{
|
||||
if (judgement != null)
|
||||
{
|
||||
|
@ -44,7 +44,10 @@ namespace osu.Game.Modes.Taiko.Beatmaps
|
||||
IHasRepeats repeatsData = original as IHasRepeats;
|
||||
IHasEndTime endTimeData = original as IHasEndTime;
|
||||
|
||||
bool strong = ((original.Sample?.Type ?? SampleType.None) & SampleType.Finish) > 0;
|
||||
// Old osu! used hit sounding to determine various hit type information
|
||||
SampleType sample = original.Sample?.Type ?? SampleType.None;
|
||||
|
||||
bool strong = (sample & SampleType.Finish) > 0;
|
||||
|
||||
if (distanceData != null)
|
||||
{
|
||||
@ -71,11 +74,23 @@ namespace osu.Game.Modes.Taiko.Beatmaps
|
||||
};
|
||||
}
|
||||
|
||||
return new Hit
|
||||
bool isCentre = (sample & ~(SampleType.Finish | SampleType.Normal)) == 0;
|
||||
|
||||
if (isCentre)
|
||||
{
|
||||
return new CentreHit
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
Sample = original.Sample,
|
||||
IsStrong = strong
|
||||
};
|
||||
}
|
||||
|
||||
return new RimHit
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
Sample = original.Sample,
|
||||
IsStrong = strong
|
||||
IsStrong = strong,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Modes.Taiko.Judgements
|
||||
/// The result value for the combo portion of the score.
|
||||
/// </summary>
|
||||
public int ResultValueForScore => NumericResultForScore(TaikoResult);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The result value for the accuracy portion of the score.
|
||||
/// </summary>
|
||||
@ -32,7 +32,7 @@ namespace osu.Game.Modes.Taiko.Judgements
|
||||
/// The maximum result value for the combo portion of the score.
|
||||
/// </summary>
|
||||
public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The maximum result value for the accuracy portion of the score.
|
||||
/// </summary>
|
||||
@ -45,7 +45,7 @@ namespace osu.Game.Modes.Taiko.Judgements
|
||||
/// <summary>
|
||||
/// Whether this Judgement has a secondary hit in the case of finishers.
|
||||
/// </summary>
|
||||
public bool SecondHit;
|
||||
public virtual bool SecondHit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Computes the numeric result value for the combo portion of the score.
|
||||
|
25
osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs
Normal file
25
osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Modes.Judgements;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Judgements
|
||||
{
|
||||
public class TaikoStrongHitJudgement : TaikoJudgement, IPartialJudgement
|
||||
{
|
||||
public bool Changed { get; set; }
|
||||
|
||||
public override bool SecondHit
|
||||
{
|
||||
get { return base.SecondHit; }
|
||||
set
|
||||
{
|
||||
if (base.SecondHit == value)
|
||||
return;
|
||||
base.SecondHit = value;
|
||||
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Modes.Mods;
|
||||
using osu.Game.Modes.Scoring;
|
||||
using osu.Game.Modes.Taiko.Objects;
|
||||
using osu.Game.Modes.Taiko.Replays;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Mods
|
||||
{
|
||||
@ -61,4 +66,13 @@ namespace osu.Game.Modes.Taiko.Mods
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class TaikoModAutoplay : ModAutoplay<TaikoHitObject>
|
||||
{
|
||||
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score
|
||||
{
|
||||
User = new User { Username = "mekkadosu!" },
|
||||
Replay = new TaikoAutoReplay(beatmap)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
9
osu.Game.Modes.Taiko/Objects/CentreHit.cs
Normal file
9
osu.Game.Modes.Taiko/Objects/CentreHit.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Objects
|
||||
{
|
||||
public class CentreHit : Hit
|
||||
{
|
||||
}
|
||||
}
|
@ -99,7 +99,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
|
||||
protected override bool HandleKeyPress(Key key)
|
||||
{
|
||||
return !Judgement.Result.HasValue && UpdateJudgement(true);
|
||||
return Judgement.Result == HitResult.None && UpdateJudgement(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
|
||||
protected override bool HandleKeyPress(Key key)
|
||||
{
|
||||
if (Judgement.Result.HasValue)
|
||||
if (Judgement.Result != HitResult.None)
|
||||
return false;
|
||||
|
||||
validKeyPressed = HitKeys.Contains(key);
|
||||
|
@ -5,6 +5,8 @@ using OpenTK.Input;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Modes.Taiko.Judgements;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
{
|
||||
@ -25,9 +27,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
{
|
||||
}
|
||||
|
||||
protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement();
|
||||
|
||||
protected override void CheckJudgement(bool userTriggered)
|
||||
{
|
||||
if (!Judgement.Result.HasValue)
|
||||
if (Judgement.Result == HitResult.None)
|
||||
{
|
||||
base.CheckJudgement(userTriggered);
|
||||
return;
|
||||
@ -45,7 +49,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
protected override bool HandleKeyPress(Key key)
|
||||
{
|
||||
// Check if we've handled the first key
|
||||
if (!Judgement.Result.HasValue)
|
||||
if (Judgement.Result == HitResult.None)
|
||||
{
|
||||
// First key hasn't been handled yet, attempt to handle it
|
||||
bool handled = base.HandleKeyPress(key);
|
||||
|
@ -219,7 +219,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
|
||||
|
||||
protected override bool HandleKeyPress(Key key)
|
||||
{
|
||||
if (Judgement.Result.HasValue)
|
||||
if (Judgement.Result != HitResult.None)
|
||||
return false;
|
||||
|
||||
// Don't handle keys before the swell starts
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces
|
||||
/// a rounded (_[-Width-]_) figure such that a regular "circle" is the result of a parent with Width = 0.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class CirclePiece : Container, IAccented
|
||||
public class CirclePiece : Container, IHasAccentColour
|
||||
{
|
||||
public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f;
|
||||
public const float SYMBOL_BORDER = 8;
|
||||
|
9
osu.Game.Modes.Taiko/Objects/RimHit.cs
Normal file
9
osu.Game.Modes.Taiko/Objects/RimHit.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Objects
|
||||
{
|
||||
public class RimHit : Hit
|
||||
{
|
||||
}
|
||||
}
|
121
osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs
Normal file
121
osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Modes.Objects.Types;
|
||||
using osu.Game.Modes.Taiko.Objects;
|
||||
using osu.Game.Modes.Replays;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Replays
|
||||
{
|
||||
public class TaikoAutoReplay : Replay
|
||||
{
|
||||
private readonly Beatmap<TaikoHitObject> beatmap;
|
||||
|
||||
public TaikoAutoReplay(Beatmap<TaikoHitObject> beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
|
||||
createAutoReplay();
|
||||
}
|
||||
|
||||
private void createAutoReplay()
|
||||
{
|
||||
bool hitButton = true;
|
||||
|
||||
Frames.Add(new ReplayFrame(-100000, 320, 240, ReplayButtonState.None));
|
||||
Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 320, 240, ReplayButtonState.None));
|
||||
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
TaikoHitObject h = beatmap.HitObjects[i];
|
||||
|
||||
ReplayButtonState button;
|
||||
|
||||
IHasEndTime endTimeData = h as IHasEndTime;
|
||||
double endTime = endTimeData?.EndTime ?? h.StartTime;
|
||||
|
||||
Swell swell = h as Swell;
|
||||
DrumRoll drumRoll = h as DrumRoll;
|
||||
Hit hit = h as Hit;
|
||||
|
||||
if (swell != null)
|
||||
{
|
||||
int d = 0;
|
||||
int count = 0;
|
||||
int req = swell.RequiredHits;
|
||||
double hitRate = swell.Duration / req;
|
||||
for (double j = h.StartTime; j < endTime; j += hitRate)
|
||||
{
|
||||
switch (d)
|
||||
{
|
||||
default:
|
||||
button = ReplayButtonState.Left1;
|
||||
break;
|
||||
case 1:
|
||||
button = ReplayButtonState.Right1;
|
||||
break;
|
||||
case 2:
|
||||
button = ReplayButtonState.Left2;
|
||||
break;
|
||||
case 3:
|
||||
button = ReplayButtonState.Right2;
|
||||
break;
|
||||
}
|
||||
|
||||
Frames.Add(new ReplayFrame(j, 0, 0, button));
|
||||
d = (d + 1) % 4;
|
||||
if (++count > req)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (drumRoll != null)
|
||||
{
|
||||
double delay = drumRoll.TickTimeDistance;
|
||||
|
||||
double time = drumRoll.StartTime;
|
||||
|
||||
for (int j = 0; j < drumRoll.TotalTicks; j++)
|
||||
{
|
||||
Frames.Add(new ReplayFrame((int)time, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2));
|
||||
time += delay;
|
||||
hitButton = !hitButton;
|
||||
}
|
||||
}
|
||||
else if (hit != null)
|
||||
{
|
||||
if (hit is CentreHit)
|
||||
{
|
||||
if (h.IsStrong)
|
||||
button = ReplayButtonState.Right1 | ReplayButtonState.Right2;
|
||||
else
|
||||
button = hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (h.IsStrong)
|
||||
button = ReplayButtonState.Left1 | ReplayButtonState.Left2;
|
||||
else
|
||||
button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
|
||||
}
|
||||
|
||||
Frames.Add(new ReplayFrame(h.StartTime, 0, 0, button));
|
||||
}
|
||||
else
|
||||
throw new Exception("Unknown hit object type.");
|
||||
|
||||
Frames.Add(new ReplayFrame(endTime + 1, 0, 0, ReplayButtonState.None));
|
||||
|
||||
if (i < beatmap.HitObjects.Count - 1)
|
||||
{
|
||||
double waitTime = beatmap.HitObjects[i + 1].StartTime - 1000;
|
||||
if (waitTime > endTime)
|
||||
Frames.Add(new ReplayFrame(waitTime, 0, 0, ReplayButtonState.None));
|
||||
}
|
||||
|
||||
hitButton = !hitButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Modes.Replays;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Input;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.Replays
|
||||
{
|
||||
internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler
|
||||
{
|
||||
public TaikoFramedReplayInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
}
|
||||
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
var keys = new List<Key>();
|
||||
|
||||
if (CurrentFrame?.MouseRight1 == true)
|
||||
keys.Add(Key.F);
|
||||
if (CurrentFrame?.MouseRight2 == true)
|
||||
keys.Add(Key.J);
|
||||
if (CurrentFrame?.MouseLeft1 == true)
|
||||
keys.Add(Key.D);
|
||||
if (CurrentFrame?.MouseLeft2 == true)
|
||||
keys.Add(Key.K);
|
||||
|
||||
return new List<InputState>
|
||||
{
|
||||
new InputState { Keyboard = new ReplayKeyboardState(keys) }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -96,9 +96,9 @@ namespace osu.Game.Modes.Taiko.Scoring
|
||||
|
||||
/// <summary>
|
||||
/// The multiple of the original score added to the combo portion of the score
|
||||
/// for correctly hitting an accented hit object with both keys.
|
||||
/// for correctly hitting a strong hit object with both keys.
|
||||
/// </summary>
|
||||
private double accentedHitScale;
|
||||
private double strongHitScale;
|
||||
|
||||
private double hpIncreaseTick;
|
||||
private double hpIncreaseGreat;
|
||||
@ -128,12 +128,12 @@ namespace osu.Game.Modes.Taiko.Scoring
|
||||
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
|
||||
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
|
||||
|
||||
var accentedHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
|
||||
var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
|
||||
|
||||
// This is a linear function that awards:
|
||||
// 10 times bonus points for hitting an accented hit object with both keys with 30 accented hit objects in the map
|
||||
// 3 times bonus points for hitting an accented hit object with both keys with 120 accented hit objects in the map
|
||||
accentedHitScale = -7d / 90d * MathHelper.Clamp(accentedHits.Count, 30, 120) + 111d / 9d;
|
||||
// 10 times bonus points for hitting a strong hit object with both keys with 30 strong hit objects in the map
|
||||
// 3 times bonus points for hitting a strong hit object with both keys with 120 strong hit objects in the map
|
||||
strongHitScale = -7d / 90d * MathHelper.Clamp(strongHits.Count, 30, 120) + 111d / 9d;
|
||||
|
||||
foreach (var obj in beatmap.HitObjects)
|
||||
{
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Modes.Taiko.Scoring
|
||||
maxComboPortion = comboPortion;
|
||||
}
|
||||
|
||||
protected override void OnNewJugement(TaikoJudgement judgement)
|
||||
protected override void OnNewJudgement(TaikoJudgement judgement)
|
||||
{
|
||||
bool isTick = judgement is TaikoDrumRollTickJudgement;
|
||||
|
||||
@ -187,29 +187,12 @@ namespace osu.Game.Modes.Taiko.Scoring
|
||||
if (!isTick)
|
||||
totalHits++;
|
||||
|
||||
// Apply combo changes, must be done before the hit score is added
|
||||
if (!isTick && judgement.Result == HitResult.Hit)
|
||||
Combo.Value++;
|
||||
|
||||
// Apply score changes
|
||||
if (judgement.Result == HitResult.Hit)
|
||||
{
|
||||
double baseValue = judgement.ResultValueForScore;
|
||||
|
||||
// Add bonus points for hitting an accented hit object with the second key
|
||||
if (judgement.SecondHit)
|
||||
baseValue += baseValue * accentedHitScale;
|
||||
|
||||
// Add score to portions
|
||||
if (isTick)
|
||||
bonusScore += baseValue;
|
||||
else
|
||||
{
|
||||
Combo.Value++;
|
||||
|
||||
// A relevance factor that needs to be applied to make higher combos more relevant
|
||||
// Value is capped at 400 combo
|
||||
double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base)));
|
||||
|
||||
comboPortion += baseValue * comboRelevance;
|
||||
}
|
||||
}
|
||||
addHitScore(judgement);
|
||||
|
||||
// Apply HP changes
|
||||
switch (judgement.Result)
|
||||
@ -235,7 +218,43 @@ namespace osu.Game.Modes.Taiko.Scoring
|
||||
break;
|
||||
}
|
||||
|
||||
// Compute the new score + accuracy
|
||||
calculateScore();
|
||||
}
|
||||
|
||||
protected override void OnJudgementChanged(TaikoJudgement judgement)
|
||||
{
|
||||
// Apply score changes
|
||||
addHitScore(judgement);
|
||||
|
||||
calculateScore();
|
||||
}
|
||||
|
||||
private void addHitScore(TaikoJudgement judgement)
|
||||
{
|
||||
if (judgement.Result != HitResult.Hit)
|
||||
return;
|
||||
|
||||
double baseValue = judgement.ResultValueForScore;
|
||||
|
||||
// Add increased score for hitting a strong hit object with the second key
|
||||
if (judgement.SecondHit)
|
||||
baseValue *= strongHitScale;
|
||||
|
||||
// Add score to portions
|
||||
if (judgement is TaikoDrumRollTickJudgement)
|
||||
bonusScore += baseValue;
|
||||
else
|
||||
{
|
||||
// A relevance factor that needs to be applied to make higher combos more relevant
|
||||
// Value is capped at 400 combo
|
||||
double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base)));
|
||||
|
||||
comboPortion += baseValue * comboRelevance;
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateScore()
|
||||
{
|
||||
int scoreForAccuracy = 0;
|
||||
int maxScoreForAccuracy = 0;
|
||||
|
||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko
|
||||
{
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new ModAutoplay(),
|
||||
new TaikoModAutoplay(),
|
||||
new ModCinema(),
|
||||
},
|
||||
},
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Modes.Replays;
|
||||
using osu.Game.Modes.Scoring;
|
||||
using osu.Game.Modes.Taiko.Beatmaps;
|
||||
using osu.Game.Modes.Taiko.Judgements;
|
||||
using osu.Game.Modes.Taiko.Objects;
|
||||
using osu.Game.Modes.Taiko.Replays;
|
||||
using osu.Game.Modes.Taiko.Scoring;
|
||||
using osu.Game.Modes.UI;
|
||||
|
||||
@ -28,5 +30,7 @@ namespace osu.Game.Modes.Taiko.UI
|
||||
protected override Playfield<TaikoHitObject, TaikoJudgement> CreatePlayfield() => new TaikoPlayfield();
|
||||
|
||||
protected override DrawableHitObject<TaikoHitObject, TaikoJudgement> GetVisualRepresentation(TaikoHitObject h) => null;
|
||||
|
||||
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,10 @@
|
||||
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
|
||||
<Compile Include="Beatmaps\TaikoBeatmapProcessor.cs" />
|
||||
<Compile Include="Judgements\TaikoDrumRollTickJudgement.cs" />
|
||||
<Compile Include="Judgements\TaikoStrongHitJudgement.cs" />
|
||||
<Compile Include="Judgements\TaikoJudgement.cs" />
|
||||
<Compile Include="Judgements\TaikoHitResult.cs" />
|
||||
<Compile Include="Objects\CentreHit.cs" />
|
||||
<Compile Include="Objects\Drawable\DrawableRimHit.cs" />
|
||||
<Compile Include="Objects\Drawable\DrawableStrongRimHit.cs" />
|
||||
<Compile Include="Objects\Drawable\DrawableCentreHit.cs" />
|
||||
@ -71,7 +73,10 @@
|
||||
<Compile Include="Objects\DrumRoll.cs" />
|
||||
<Compile Include="Objects\DrumRollTick.cs" />
|
||||
<Compile Include="Objects\Hit.cs" />
|
||||
<Compile Include="Objects\RimHit.cs" />
|
||||
<Compile Include="Objects\Swell.cs" />
|
||||
<Compile Include="Replays\TaikoFramedReplayInputHandler.cs" />
|
||||
<Compile Include="Replays\TaikoAutoReplay.cs" />
|
||||
<Compile Include="Objects\TaikoHitObject.cs" />
|
||||
<Compile Include="TaikoDifficultyCalculator.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Database
|
||||
|
||||
using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize))
|
||||
using (var reader = new StreamReader(lzma))
|
||||
score.Replay = score.CreateLegacyReplayFrom(reader);
|
||||
score.Replay = score.CreateReplay(reader);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,25 +12,23 @@ namespace osu.Game.Graphics
|
||||
/// The accent colour is used to colorize various objects inside a drawable
|
||||
/// without colorizing the drawable itself.
|
||||
/// </summary>
|
||||
public interface IAccented : IDrawable
|
||||
public interface IHasAccentColour : IDrawable
|
||||
{
|
||||
Color4 AccentColour { get; set; }
|
||||
}
|
||||
|
||||
public static class AccentedExtensions
|
||||
public static class AccentedColourExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Tweens the accent colour of a drawable to another colour.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDrawable">The type of drawable.</typeparam>
|
||||
/// <param name="drawable">The drawable to apply the accent colour to.</param>
|
||||
/// <param name="accentedDrawable">The drawable to apply the accent colour to.</param>
|
||||
/// <param name="newColour">The new accent colour.</param>
|
||||
/// <param name="duration">The tween duration.</param>
|
||||
/// <param name="easing">The tween easing.</param>
|
||||
public static void FadeAccent<TDrawable>(this TDrawable drawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None)
|
||||
where TDrawable : Drawable, IAccented
|
||||
public static void FadeAccent(this IHasAccentColour accentedDrawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None)
|
||||
{
|
||||
drawable.TransformTo(drawable.AccentColour, newColour, duration, easing, new TransformAccent());
|
||||
accentedDrawable.TransformTo(accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent());
|
||||
}
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Graphics.Transforms
|
||||
{
|
||||
base.Apply(d);
|
||||
|
||||
var accented = d as IAccented;
|
||||
var accented = d as IHasAccentColour;
|
||||
if (accented != null)
|
||||
accented.AccentColour = CurrentValue;
|
||||
}
|
||||
|
26
osu.Game/Modes/Judgements/IPartialJudgement.cs
Normal file
26
osu.Game/Modes/Judgements/IPartialJudgement.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Modes.Scoring;
|
||||
|
||||
namespace osu.Game.Modes.Judgements
|
||||
{
|
||||
/// <summary>
|
||||
/// Inidicates that the judgement this is attached to is a partial judgement and the scoring value may change.
|
||||
/// <para>
|
||||
/// This judgement will be continually processed by <see cref="DrawableHitObject{TObject, TJudgement}.CheckJudgement(bool)"/>
|
||||
/// unless the result is a miss and will trigger a full re-process of the <see cref="ScoreProcessor"/> when changed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IPartialJudgement
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that this partial judgement has changed and requires a full re-process of the <see cref="ScoreProcessor"/>.
|
||||
/// <para>
|
||||
/// This is set to false once the judgement has been re-processed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
bool Changed { get; set; }
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ namespace osu.Game.Modes.Judgements
|
||||
/// <summary>
|
||||
/// Whether this judgement is the result of a hit or a miss.
|
||||
/// </summary>
|
||||
public HitResult? Result;
|
||||
public HitResult Result;
|
||||
|
||||
/// <summary>
|
||||
/// The offset at which this judgement occurred.
|
||||
@ -20,7 +20,7 @@ namespace osu.Game.Modes.Judgements
|
||||
/// <summary>
|
||||
/// The combo after this judgement was processed.
|
||||
/// </summary>
|
||||
public ulong? ComboAtHit;
|
||||
public int ComboAtHit;
|
||||
|
||||
/// <summary>
|
||||
/// The string representation for the result achieved.
|
||||
|
@ -1,268 +0,0 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.IO.Legacy;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using KeyboardState = osu.Framework.Input.KeyboardState;
|
||||
using MouseState = osu.Framework.Input.MouseState;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
public class LegacyReplay : Replay
|
||||
{
|
||||
protected List<LegacyReplayFrame> Frames = new List<LegacyReplayFrame>();
|
||||
|
||||
protected LegacyReplay()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LegacyReplay(StreamReader reader)
|
||||
{
|
||||
float lastTime = 0;
|
||||
|
||||
foreach (var l in reader.ReadToEnd().Split(','))
|
||||
{
|
||||
var split = l.Split('|');
|
||||
|
||||
if (split.Length < 4 || float.Parse(split[0]) < 0) continue;
|
||||
|
||||
lastTime += float.Parse(split[0]);
|
||||
|
||||
Frames.Add(new LegacyReplayFrame(
|
||||
lastTime,
|
||||
float.Parse(split[1]),
|
||||
384 - float.Parse(split[2]),
|
||||
(LegacyButtonState)int.Parse(split[3])
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public override ReplayInputHandler CreateInputHandler() => new LegacyReplayInputHandler(Frames);
|
||||
|
||||
/// <summary>
|
||||
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
|
||||
/// It handles logic of any frames which *must* be executed.
|
||||
/// </summary>
|
||||
protected class LegacyReplayInputHandler : ReplayInputHandler
|
||||
{
|
||||
private readonly List<LegacyReplayFrame> replayContent;
|
||||
|
||||
public LegacyReplayFrame CurrentFrame => !hasFrames ? null : replayContent[currentFrameIndex];
|
||||
public LegacyReplayFrame NextFrame => !hasFrames ? null : replayContent[nextFrameIndex];
|
||||
|
||||
private int currentFrameIndex;
|
||||
|
||||
private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, replayContent.Count - 1);
|
||||
|
||||
public LegacyReplayInputHandler(List<LegacyReplayFrame> replayContent)
|
||||
{
|
||||
this.replayContent = replayContent;
|
||||
}
|
||||
|
||||
private bool advanceFrame()
|
||||
{
|
||||
int newFrame = nextFrameIndex;
|
||||
|
||||
//ensure we aren't at an extent.
|
||||
if (newFrame == currentFrameIndex) return false;
|
||||
|
||||
currentFrameIndex = newFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 pos)
|
||||
{
|
||||
}
|
||||
|
||||
private Vector2? position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!hasFrames)
|
||||
return null;
|
||||
|
||||
return Interpolation.ValueAt(currentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
|
||||
}
|
||||
}
|
||||
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
var buttons = new HashSet<MouseButton>();
|
||||
if (CurrentFrame?.MouseLeft ?? false)
|
||||
buttons.Add(MouseButton.Left);
|
||||
if (CurrentFrame?.MouseRight ?? false)
|
||||
buttons.Add(MouseButton.Right);
|
||||
|
||||
return new List<InputState>
|
||||
{
|
||||
new InputState
|
||||
{
|
||||
Mouse = new ReplayMouseState(ToScreenSpace(position ?? Vector2.Zero), buttons),
|
||||
Keyboard = new ReplayKeyboardState(new List<Key>())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AtLastFrame => currentFrameIndex == replayContent.Count - 1;
|
||||
public bool AtFirstFrame => currentFrameIndex == 0;
|
||||
|
||||
public Vector2 Size => new Vector2(512, 384);
|
||||
|
||||
private const double sixty_frame_time = 1000.0 / 60;
|
||||
|
||||
private double currentTime;
|
||||
private int currentDirection;
|
||||
|
||||
/// <summary>
|
||||
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
||||
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
||||
/// </summary>
|
||||
public bool FrameAccuratePlayback = true;
|
||||
|
||||
private bool hasFrames => replayContent.Count > 0;
|
||||
|
||||
private bool inImportantSection =>
|
||||
FrameAccuratePlayback &&
|
||||
//a button is in a pressed state
|
||||
(currentDirection > 0 ? CurrentFrame : NextFrame)?.ButtonState > LegacyButtonState.None &&
|
||||
//the next frame is within an allowable time span
|
||||
Math.Abs(currentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2;
|
||||
|
||||
/// <summary>
|
||||
/// Update the current frame based on an incoming time value.
|
||||
/// There are cases where we return a "must-use" time value that is different from the input.
|
||||
/// This is to ensure accurate playback of replay data.
|
||||
/// </summary>
|
||||
/// <param name="time">The time which we should use for finding the current frame.</param>
|
||||
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
||||
public override double? SetFrameFromTime(double time)
|
||||
{
|
||||
currentDirection = time.CompareTo(currentTime);
|
||||
if (currentDirection == 0) currentDirection = 1;
|
||||
|
||||
if (hasFrames)
|
||||
{
|
||||
//if we changed frames, we want to execute once *exactly* on the frame's time.
|
||||
if (currentDirection == time.CompareTo(NextFrame.Time) && advanceFrame())
|
||||
return currentTime = CurrentFrame.Time;
|
||||
|
||||
//if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null.
|
||||
if (inImportantSection)
|
||||
return null;
|
||||
}
|
||||
|
||||
return currentTime = time;
|
||||
}
|
||||
|
||||
protected class ReplayMouseState : MouseState
|
||||
{
|
||||
public ReplayMouseState(Vector2 position, IEnumerable<MouseButton> list)
|
||||
{
|
||||
Position = position;
|
||||
list.ForEach(b => PressedButtons.Add(b));
|
||||
}
|
||||
}
|
||||
|
||||
protected class ReplayKeyboardState : KeyboardState
|
||||
{
|
||||
public ReplayKeyboardState(List<Key> keys)
|
||||
{
|
||||
Keys = keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
protected enum LegacyButtonState
|
||||
{
|
||||
None = 0,
|
||||
Left1 = 1,
|
||||
Right1 = 2,
|
||||
Left2 = 4,
|
||||
Right2 = 8,
|
||||
Smoke = 16
|
||||
}
|
||||
|
||||
protected class LegacyReplayFrame
|
||||
{
|
||||
public Vector2 Position => new Vector2(MouseX, MouseY);
|
||||
|
||||
public float MouseX;
|
||||
public float MouseY;
|
||||
public bool MouseLeft;
|
||||
public bool MouseRight;
|
||||
public bool MouseLeft1;
|
||||
public bool MouseRight1;
|
||||
public bool MouseLeft2;
|
||||
public bool MouseRight2;
|
||||
public LegacyButtonState ButtonState;
|
||||
public double Time;
|
||||
|
||||
public LegacyReplayFrame(double time, float posX, float posY, LegacyButtonState buttonState)
|
||||
{
|
||||
MouseX = posX;
|
||||
MouseY = posY;
|
||||
ButtonState = buttonState;
|
||||
SetButtonStates(buttonState);
|
||||
Time = time;
|
||||
}
|
||||
|
||||
public void SetButtonStates(LegacyButtonState buttonState)
|
||||
{
|
||||
ButtonState = buttonState;
|
||||
MouseLeft = (buttonState & (LegacyButtonState.Left1 | LegacyButtonState.Left2)) > 0;
|
||||
MouseLeft1 = (buttonState & LegacyButtonState.Left1) > 0;
|
||||
MouseLeft2 = (buttonState & LegacyButtonState.Left2) > 0;
|
||||
MouseRight = (buttonState & (LegacyButtonState.Right1 | LegacyButtonState.Right2)) > 0;
|
||||
MouseRight1 = (buttonState & LegacyButtonState.Right1) > 0;
|
||||
MouseRight2 = (buttonState & LegacyButtonState.Right2) > 0;
|
||||
}
|
||||
|
||||
public LegacyReplayFrame(Stream s) : this(new SerializationReader(s))
|
||||
{
|
||||
}
|
||||
|
||||
public LegacyReplayFrame(SerializationReader sr)
|
||||
{
|
||||
ButtonState = (LegacyButtonState)sr.ReadByte();
|
||||
SetButtonStates(ButtonState);
|
||||
|
||||
byte bt = sr.ReadByte();
|
||||
if (bt > 0)//Handle Pre-Taiko compatible replays.
|
||||
SetButtonStates(LegacyButtonState.Right1);
|
||||
|
||||
MouseX = sr.ReadSingle();
|
||||
MouseY = sr.ReadSingle();
|
||||
Time = sr.ReadInt32();
|
||||
}
|
||||
|
||||
public void ReadFromStream(SerializationReader sr)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void WriteToStream(SerializationWriter sw)
|
||||
{
|
||||
sw.Write((byte)ButtonState);
|
||||
sw.Write((byte)0);
|
||||
sw.Write(MouseX);
|
||||
sw.Write(MouseY);
|
||||
sw.Write(Time);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -157,7 +157,7 @@ namespace osu.Game.Modes.Mods
|
||||
|
||||
public void Apply(HitRenderer<T> hitRenderer)
|
||||
{
|
||||
hitRenderer.InputManager.ReplayInputHandler = CreateReplayScore(hitRenderer.Beatmap)?.Replay?.CreateInputHandler();
|
||||
hitRenderer.SetReplay(CreateReplayScore(hitRenderer.Beatmap)?.Replay);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,16 +93,26 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
/// <returns>Whether a hit was processed.</returns>
|
||||
protected bool UpdateJudgement(bool userTriggered)
|
||||
{
|
||||
if (Judgement.Result != null)
|
||||
IPartialJudgement partial = Judgement as IPartialJudgement;
|
||||
|
||||
// Never re-process non-partial hits
|
||||
if (Judgement.Result != HitResult.None && partial == null)
|
||||
return false;
|
||||
|
||||
// Update the judgement state
|
||||
double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
||||
|
||||
Judgement.TimeOffset = Time.Current - endTime;
|
||||
|
||||
// Update the judgement state
|
||||
bool hadResult = Judgement.Result != HitResult.None;
|
||||
CheckJudgement(userTriggered);
|
||||
|
||||
if (Judgement.Result == null)
|
||||
// Don't process judgements with no result
|
||||
if (Judgement.Result == HitResult.None)
|
||||
return false;
|
||||
|
||||
// Don't process judgements that previously had results but the results were unchanged
|
||||
if (hadResult && partial?.Changed != true)
|
||||
return false;
|
||||
|
||||
switch (Judgement.Result)
|
||||
@ -117,6 +127,9 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
|
||||
OnJudgement?.Invoke(this);
|
||||
|
||||
if (partial != null)
|
||||
partial.Changed = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,19 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
{
|
||||
public enum HitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the object has not been judged yet.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
None,
|
||||
/// <summary>
|
||||
/// Indicates that the object has been judged as a miss.
|
||||
/// </summary>
|
||||
[Description(@"Miss")]
|
||||
Miss,
|
||||
/// <summary>
|
||||
/// Indicates that the object has been judged as a hit.
|
||||
/// </summary>
|
||||
[Description(@"Hit")]
|
||||
Hit,
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Input.Handlers;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
public abstract class Replay
|
||||
{
|
||||
public virtual ReplayInputHandler CreateInputHandler() => null;
|
||||
}
|
||||
}
|
151
osu.Game/Modes/Replays/FramedReplayInputHandler.cs
Normal file
151
osu.Game/Modes/Replays/FramedReplayInputHandler.cs
Normal file
@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Input.Handlers;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using KeyboardState = osu.Framework.Input.KeyboardState;
|
||||
using MouseState = osu.Framework.Input.MouseState;
|
||||
|
||||
namespace osu.Game.Modes.Replays
|
||||
{
|
||||
/// <summary>
|
||||
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
|
||||
/// It handles logic of any frames which *must* be executed.
|
||||
/// </summary>
|
||||
public class FramedReplayInputHandler : ReplayInputHandler
|
||||
{
|
||||
private readonly Replay replay;
|
||||
|
||||
protected List<ReplayFrame> Frames => replay.Frames;
|
||||
|
||||
public ReplayFrame CurrentFrame => !hasFrames ? null : Frames[currentFrameIndex];
|
||||
public ReplayFrame NextFrame => !hasFrames ? null : Frames[nextFrameIndex];
|
||||
|
||||
private int currentFrameIndex;
|
||||
|
||||
private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1);
|
||||
|
||||
public FramedReplayInputHandler(Replay replay)
|
||||
{
|
||||
this.replay = replay;
|
||||
}
|
||||
|
||||
private bool advanceFrame()
|
||||
{
|
||||
int newFrame = nextFrameIndex;
|
||||
|
||||
//ensure we aren't at an extent.
|
||||
if (newFrame == currentFrameIndex) return false;
|
||||
|
||||
currentFrameIndex = newFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 pos)
|
||||
{
|
||||
}
|
||||
|
||||
private Vector2? position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!hasFrames)
|
||||
return null;
|
||||
|
||||
return Interpolation.ValueAt(currentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
|
||||
}
|
||||
}
|
||||
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
var buttons = new HashSet<MouseButton>();
|
||||
if (CurrentFrame?.MouseLeft ?? false)
|
||||
buttons.Add(MouseButton.Left);
|
||||
if (CurrentFrame?.MouseRight ?? false)
|
||||
buttons.Add(MouseButton.Right);
|
||||
|
||||
return new List<InputState>
|
||||
{
|
||||
new InputState
|
||||
{
|
||||
Mouse = new ReplayMouseState(ToScreenSpace(position ?? Vector2.Zero), buttons),
|
||||
Keyboard = new ReplayKeyboardState(new List<Key>())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
|
||||
public bool AtFirstFrame => currentFrameIndex == 0;
|
||||
|
||||
public Vector2 Size => new Vector2(512, 384);
|
||||
|
||||
private const double sixty_frame_time = 1000.0 / 60;
|
||||
|
||||
private double currentTime;
|
||||
private int currentDirection;
|
||||
|
||||
/// <summary>
|
||||
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
||||
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
||||
/// </summary>
|
||||
public bool FrameAccuratePlayback = true;
|
||||
|
||||
private bool hasFrames => Frames.Count > 0;
|
||||
|
||||
private bool inImportantSection =>
|
||||
FrameAccuratePlayback &&
|
||||
//a button is in a pressed state
|
||||
((currentDirection > 0 ? CurrentFrame : NextFrame)?.IsImportant ?? false) &&
|
||||
//the next frame is within an allowable time span
|
||||
Math.Abs(currentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2;
|
||||
|
||||
/// <summary>
|
||||
/// Update the current frame based on an incoming time value.
|
||||
/// There are cases where we return a "must-use" time value that is different from the input.
|
||||
/// This is to ensure accurate playback of replay data.
|
||||
/// </summary>
|
||||
/// <param name="time">The time which we should use for finding the current frame.</param>
|
||||
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
||||
public override double? SetFrameFromTime(double time)
|
||||
{
|
||||
currentDirection = time.CompareTo(currentTime);
|
||||
if (currentDirection == 0) currentDirection = 1;
|
||||
|
||||
if (hasFrames)
|
||||
{
|
||||
//if we changed frames, we want to execute once *exactly* on the frame's time.
|
||||
if (currentDirection == time.CompareTo(NextFrame.Time) && advanceFrame())
|
||||
return currentTime = CurrentFrame.Time;
|
||||
|
||||
//if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null.
|
||||
if (inImportantSection)
|
||||
return null;
|
||||
}
|
||||
|
||||
return currentTime = time;
|
||||
}
|
||||
|
||||
protected class ReplayMouseState : MouseState
|
||||
{
|
||||
public ReplayMouseState(Vector2 position, IEnumerable<MouseButton> list)
|
||||
{
|
||||
Position = position;
|
||||
list.ForEach(b => PressedButtons.Add(b));
|
||||
}
|
||||
}
|
||||
|
||||
protected class ReplayKeyboardState : KeyboardState
|
||||
{
|
||||
public ReplayKeyboardState(List<Key> keys)
|
||||
{
|
||||
Keys = keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
osu.Game/Modes/Replays/Replay.cs
Normal file
12
osu.Game/Modes/Replays/Replay.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Modes.Replays
|
||||
{
|
||||
public class Replay
|
||||
{
|
||||
public List<ReplayFrame> Frames = new List<ReplayFrame>();
|
||||
}
|
||||
}
|
18
osu.Game/Modes/Replays/ReplayButtonState.cs
Normal file
18
osu.Game/Modes/Replays/ReplayButtonState.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Modes.Replays
|
||||
{
|
||||
[Flags]
|
||||
public enum ReplayButtonState
|
||||
{
|
||||
None = 0,
|
||||
Left1 = 1,
|
||||
Right1 = 2,
|
||||
Left2 = 4,
|
||||
Right2 = 8,
|
||||
Smoke = 16
|
||||
}
|
||||
}
|
71
osu.Game/Modes/Replays/ReplayFrame.cs
Normal file
71
osu.Game/Modes/Replays/ReplayFrame.cs
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Modes.Replays
|
||||
{
|
||||
public class ReplayFrame
|
||||
{
|
||||
public Vector2 Position => new Vector2(MouseX, MouseY);
|
||||
|
||||
public bool IsImportant => MouseLeft || MouseRight;
|
||||
|
||||
public float MouseX;
|
||||
public float MouseY;
|
||||
|
||||
public bool MouseLeft => MouseLeft1 || MouseLeft2;
|
||||
public bool MouseRight => MouseRight1 || MouseRight2;
|
||||
|
||||
public bool MouseLeft1
|
||||
{
|
||||
get { return (ButtonState & ReplayButtonState.Left1) > 0; }
|
||||
set { setButtonState(ReplayButtonState.Left1, value); }
|
||||
}
|
||||
public bool MouseRight1
|
||||
{
|
||||
get { return (ButtonState & ReplayButtonState.Right1) > 0; }
|
||||
set { setButtonState(ReplayButtonState.Right1, value); }
|
||||
}
|
||||
public bool MouseLeft2
|
||||
{
|
||||
get { return (ButtonState & ReplayButtonState.Left2) > 0; }
|
||||
set { setButtonState(ReplayButtonState.Left2, value); }
|
||||
}
|
||||
public bool MouseRight2
|
||||
{
|
||||
get { return (ButtonState & ReplayButtonState.Right2) > 0; }
|
||||
set { setButtonState(ReplayButtonState.Right2, value); }
|
||||
}
|
||||
|
||||
private void setButtonState(ReplayButtonState singleButton, bool pressed)
|
||||
{
|
||||
if (pressed)
|
||||
ButtonState |= singleButton;
|
||||
else
|
||||
ButtonState &= ~singleButton;
|
||||
}
|
||||
|
||||
public double Time;
|
||||
|
||||
public ReplayButtonState ButtonState;
|
||||
|
||||
protected ReplayFrame()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ReplayFrame(double time, float posX, float posY, ReplayButtonState buttonState)
|
||||
{
|
||||
MouseX = posX;
|
||||
MouseY = posY;
|
||||
ButtonState = buttonState;
|
||||
Time = time;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Modes.Mods;
|
||||
using osu.Game.Users;
|
||||
using System.IO;
|
||||
using osu.Game.Modes.Replays;
|
||||
|
||||
namespace osu.Game.Modes.Scoring
|
||||
{
|
||||
@ -45,11 +47,34 @@ namespace osu.Game.Modes.Scoring
|
||||
public DateTime Date;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy replay which is read from a stream.
|
||||
/// Creates a replay which is read from a stream.
|
||||
/// </summary>
|
||||
/// <param name="reader">The stream reader.</param>
|
||||
/// <returns>The replay.</returns>
|
||||
public virtual Replay CreateLegacyReplayFrom(StreamReader reader) => new LegacyReplay(reader);
|
||||
public virtual Replay CreateReplay(StreamReader reader)
|
||||
{
|
||||
var frames = new List<ReplayFrame>();
|
||||
|
||||
float lastTime = 0;
|
||||
|
||||
foreach (var l in reader.ReadToEnd().Split(','))
|
||||
{
|
||||
var split = l.Split('|');
|
||||
|
||||
if (split.Length < 4 || float.Parse(split[0]) < 0) continue;
|
||||
|
||||
lastTime += float.Parse(split[0]);
|
||||
|
||||
frames.Add(new ReplayFrame(
|
||||
lastTime,
|
||||
float.Parse(split[1]),
|
||||
384 - float.Parse(split[2]),
|
||||
(ReplayButtonState)int.Parse(split[3])
|
||||
));
|
||||
}
|
||||
|
||||
return new Replay { Frames = frames };
|
||||
}
|
||||
|
||||
// [JsonProperty(@"count50")] 0,
|
||||
//[JsonProperty(@"count100")] 0,
|
||||
|
@ -141,11 +141,17 @@ namespace osu.Game.Modes.Scoring
|
||||
/// <param name="judgement">The judgement to add.</param>
|
||||
protected void AddJudgement(TJudgement judgement)
|
||||
{
|
||||
Judgements.Add(judgement);
|
||||
bool exists = Judgements.Contains(judgement);
|
||||
|
||||
OnNewJugement(judgement);
|
||||
if (!exists)
|
||||
{
|
||||
Judgements.Add(judgement);
|
||||
OnNewJudgement(judgement);
|
||||
|
||||
judgement.ComboAtHit = (ulong)Combo.Value;
|
||||
judgement.ComboAtHit = Combo.Value;
|
||||
}
|
||||
else
|
||||
OnJudgementChanged(judgement);
|
||||
|
||||
UpdateFailed();
|
||||
}
|
||||
@ -158,9 +164,21 @@ namespace osu.Game.Modes.Scoring
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update any values that potentially need post-processing on a judgement change.
|
||||
/// Updates any values that need post-processing. Invoked when a new judgement has occurred.
|
||||
/// <para>
|
||||
/// This is not triggered when existing judgements are changed - for that see <see cref="OnJudgementChanged(TJudgement)"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="judgement">The judgement that triggered this calculation.</param>
|
||||
protected abstract void OnNewJugement(TJudgement judgement);
|
||||
protected abstract void OnNewJudgement(TJudgement judgement);
|
||||
|
||||
/// <summary>
|
||||
/// Updates any values that need post-processing. Invoked when an existing judgement has changed.
|
||||
/// <para>
|
||||
/// This is not triggered when a new judgement has occurred - for that see <see cref="OnNewJudgement(TJudgement)"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="judgement">The judgement that triggered this calculation.</param>
|
||||
protected virtual void OnJudgementChanged(TJudgement judgement) { }
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Game.Modes.Replays;
|
||||
using osu.Game.Modes.Scoring;
|
||||
|
||||
namespace osu.Game.Modes.UI
|
||||
@ -68,6 +69,14 @@ namespace osu.Game.Modes.UI
|
||||
/// </summary>
|
||||
/// <returns>The input manager.</returns>
|
||||
protected virtual KeyConversionInputManager CreateKeyConversionInputManager() => new KeyConversionInputManager();
|
||||
|
||||
protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a replay to be used, overriding local input.
|
||||
/// </summary>
|
||||
/// <param name="replay">The replay, null for local input.</param>
|
||||
public void SetReplay(Replay replay) => InputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -149,7 +158,7 @@ namespace osu.Game.Modes.UI
|
||||
public event Action<TJudgement> OnJudgement;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result.HasValue);
|
||||
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result != HitResult.None);
|
||||
|
||||
/// <summary>
|
||||
/// The playfield.
|
||||
|
@ -125,7 +125,7 @@ namespace osu.Game
|
||||
|
||||
Beatmap.Value = BeatmapDatabase.GetWorkingBeatmap(s.Beatmap);
|
||||
|
||||
menu.Push(new PlayerLoader(new Player { ReplayInputHandler = s.Replay.CreateInputHandler() }));
|
||||
menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay)));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -15,7 +15,6 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Modes;
|
||||
using osu.Game.Modes.UI;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
internal override bool HasLocalCursorDisplayed => !hasReplayLoaded && !IsPaused;
|
||||
|
||||
private bool hasReplayLoaded => hitRenderer.InputManager.ReplayInputHandler != null;
|
||||
private bool hasReplayLoaded => HitRenderer.InputManager.ReplayInputHandler != null;
|
||||
|
||||
public BeatmapInfo BeatmapInfo;
|
||||
|
||||
@ -53,7 +52,7 @@ namespace osu.Game.Screens.Play
|
||||
private Ruleset ruleset;
|
||||
|
||||
private ScoreProcessor scoreProcessor;
|
||||
private HitRenderer hitRenderer;
|
||||
protected HitRenderer HitRenderer;
|
||||
private Bindable<int> dimLevel;
|
||||
private SkipButton skipButton;
|
||||
|
||||
@ -112,9 +111,9 @@ namespace osu.Game.Screens.Play
|
||||
});
|
||||
|
||||
ruleset = Ruleset.GetRuleset(Beatmap.PlayMode);
|
||||
hitRenderer = ruleset.CreateHitRendererWith(Beatmap);
|
||||
HitRenderer = ruleset.CreateHitRendererWith(Beatmap);
|
||||
|
||||
scoreProcessor = hitRenderer.CreateScoreProcessor();
|
||||
scoreProcessor = HitRenderer.CreateScoreProcessor();
|
||||
|
||||
hudOverlay = new StandardHudOverlay();
|
||||
hudOverlay.KeyCounter.Add(ruleset.CreateGameplayKeys());
|
||||
@ -133,13 +132,10 @@ namespace osu.Game.Screens.Play
|
||||
};
|
||||
|
||||
|
||||
if (ReplayInputHandler != null)
|
||||
hitRenderer.InputManager.ReplayInputHandler = ReplayInputHandler;
|
||||
|
||||
hudOverlay.BindHitRenderer(hitRenderer);
|
||||
hudOverlay.BindHitRenderer(HitRenderer);
|
||||
|
||||
//bind HitRenderer to ScoreProcessor and ourselves (for a pass situation)
|
||||
hitRenderer.OnAllJudged += onCompletion;
|
||||
HitRenderer.OnAllJudged += onCompletion;
|
||||
|
||||
//bind ScoreProcessor to ourselves (for a fail situation)
|
||||
scoreProcessor.Failed += onFail;
|
||||
@ -152,7 +148,7 @@ namespace osu.Game.Screens.Play
|
||||
Clock = interpolatedSourceClock,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitRenderer,
|
||||
HitRenderer,
|
||||
skipButton = new SkipButton
|
||||
{
|
||||
Alpha = 0
|
||||
@ -336,8 +332,6 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
|
||||
public ReplayInputHandler ReplayInputHandler;
|
||||
|
||||
protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value && !IsPaused;
|
||||
}
|
||||
}
|
23
osu.Game/Screens/Play/ReplayPlayer.cs
Normal file
23
osu.Game/Screens/Play/ReplayPlayer.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Modes.Replays;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class ReplayPlayer : Player
|
||||
{
|
||||
public Replay Replay;
|
||||
|
||||
public ReplayPlayer(Replay replay)
|
||||
{
|
||||
Replay = replay;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
HitRenderer.SetReplay(Replay);
|
||||
}
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@
|
||||
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
|
||||
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
|
||||
<Compile Include="Graphics\Cursor\GameplayCursor.cs" />
|
||||
<Compile Include="Graphics\IAccented.cs" />
|
||||
<Compile Include="Graphics\IHasAccentColour.cs" />
|
||||
<Compile Include="Graphics\Sprites\OsuSpriteText.cs" />
|
||||
<Compile Include="Graphics\Transforms\TransformAccent.cs" />
|
||||
<Compile Include="Graphics\UserInterface\BackButton.cs" />
|
||||
@ -98,8 +98,10 @@
|
||||
<Compile Include="IO\Legacy\SerializationReader.cs" />
|
||||
<Compile Include="IO\Legacy\SerializationWriter.cs" />
|
||||
<Compile Include="IPC\ScoreIPCChannel.cs" />
|
||||
<Compile Include="Modes\Replays\Replay.cs" />
|
||||
<Compile Include="Modes\Judgements\DrawableJudgement.cs" />
|
||||
<Compile Include="Modes\LegacyReplay.cs" />
|
||||
<Compile Include="Modes\Judgements\IPartialJudgement.cs" />
|
||||
<Compile Include="Modes\Replays\FramedReplayInputHandler.cs" />
|
||||
<Compile Include="Modes\Mods\IApplicableMod.cs" />
|
||||
<Compile Include="Modes\Mods\ModType.cs" />
|
||||
<Compile Include="Modes\Objects\Drawables\ArmedState.cs" />
|
||||
@ -125,7 +127,8 @@
|
||||
<Compile Include="Modes\Objects\Types\IHasPosition.cs" />
|
||||
<Compile Include="Modes\Objects\Types\IHasHold.cs" />
|
||||
<Compile Include="Modes\Objects\Legacy\LegacyHitObjectType.cs" />
|
||||
<Compile Include="Modes\Replay.cs" />
|
||||
<Compile Include="Modes\Replays\ReplayButtonState.cs" />
|
||||
<Compile Include="Modes\Replays\ReplayFrame.cs" />
|
||||
<Compile Include="Modes\Scoring\Score.cs" />
|
||||
<Compile Include="Modes\Scoring\ScoreProcessor.cs" />
|
||||
<Compile Include="Modes\UI\HealthDisplay.cs" />
|
||||
@ -200,6 +203,7 @@
|
||||
<Compile Include="Screens\Play\KeyConversionInputManager.cs" />
|
||||
<Compile Include="Screens\Play\PlayerInputManager.cs" />
|
||||
<Compile Include="Screens\Play\PlayerLoader.cs" />
|
||||
<Compile Include="Screens\Play\ReplayPlayer.cs" />
|
||||
<Compile Include="Screens\Play\SkipButton.cs" />
|
||||
<Compile Include="Modes\UI\StandardComboCounter.cs" />
|
||||
<Compile Include="Screens\Select\BeatmapCarousel.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user