1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-16 02:52:35 +08:00

Compare commits

...

217 Commits

105 changed files with 2728 additions and 1303 deletions
+11
View File
@@ -3,6 +3,9 @@
"configurations": [
{
"name": "Launch VisualTests",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Desktop.VisualTests/bin/Debug/osu!.exe",
@@ -15,6 +18,9 @@
},
{
"name": "Launch Desktop",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe",
@@ -27,6 +33,11 @@
},
{
"name": "Attach",
"windows": {
"type": "clr",
"request": "attach",
"processName": "osu!"
},
"type": "mono",
"request": "attach",
"address": "localhost",
+32 -2
View File
@@ -10,9 +10,39 @@
"showOutput": "silent",
"command": "msbuild",
"args": [
// Ask msbuild to generate full paths for file names.
"/property:GenerateFullPaths=true"
"/property:GenerateFullPaths=true",
"/property:DebugType=portable"
],
"windows": {
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/m" //parallel compiling support. doesn't work well with mono atm
]
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile",
"isBuildCommand": true
},
{
"taskName": "rebuild",
"isShellCommand": true,
"showOutput": "silent",
"command": "msbuild",
"args": [
// Ask msbuild to generate full paths for file names.
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/target:Clean,Build"
],
"windows": {
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/target:Clean,Build",
"/m" //parallel compiling support. doesn't work well with mono atm
]
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile",
"isBuildCommand": true
+4 -6
View File
@@ -1,6 +1,4 @@
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu)
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[osu! on the web](https://osu.ppy.sh) | [dev chat](https://discord.gg/ppy)
@@ -12,14 +10,14 @@ This is still heavily under development and is not intended for end-user use. Th
# Requirements
- A desktop platform which can compile .NET 4.5.
- Visual Studio or MonoDevelop is recommended.
- A desktop platform which can compile .NET 4.5 (tested on macOS, linux and windows). We recommend using [Visual Studio Code](https://code.visualstudio.com/) (all platforms) or [Visual Studio Community Edition](https://www.visualstudio.com/) (windows only), both of which are free.
- Make sure you initialise and keep submodules up-to-date.
# Contributing
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://goo.gl/nFdoyI). If you're unsure of what you can help with, check out the [list](https://github.com/ppy/osu/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abounty) of available issues with bounty.
Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://www.bountysource.com/teams/ppy). If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu-framework/issues).
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
@@ -62,15 +61,12 @@ namespace osu.Desktop.VisualTests.Tests
add(new DrawableSlider(new Slider
{
StartTime = framedClock.CurrentTime + 600,
CurveObject = new CurvedHitObject
ControlPoints = new List<Vector2>
{
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400,
Position = new Vector2(-200, 0),
Velocity = 1,
TickDistance = 100,
@@ -38,9 +38,9 @@ namespace osu.Desktop.VisualTests.Tests
Origin = Anchor.TopLeft,
});
AddStep("Toggle Bar", progress.ToggleBar);
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep(5);
AddStep("Toggle Bar", progress.ToggleBar);
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep(2);
AddRepeatStep("New Values", displayNewValues, 5);
@@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
{
IHasCurve curveData = original as IHasCurve;
IHasEndTime endTimeData = original as IHasEndTime;
IHasPosition positionData = original as IHasPosition;
IHasCombo comboData = original as IHasCombo;
var curveData = original as IHasCurve;
var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition;
var comboData = original as IHasCombo;
if (curveData != null)
{
@@ -30,7 +30,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
StartTime = original.StartTime,
Samples = original.Samples,
CurveObject = curveData,
ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType,
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false
};
+2 -1
View File
@@ -3,6 +3,7 @@
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using System;
@@ -95,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score
{
Replay = new OsuAutoReplay(beatmap)
Replay = new OsuAutoGenerator(beatmap).Generate()
};
}
@@ -12,16 +12,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
public class FollowPoint : Container
{
public double StartTime;
public double EndTime;
public Vector2 EndPosition;
private const float width = 8;
public FollowPoint()
{
Origin = Anchor.Centre;
Alpha = 0;
Masking = true;
AutoSizeAxes = Axes.Both;
@@ -45,22 +40,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Delay(StartTime);
FadeIn(DrawableOsuHitObject.TIME_FADEIN);
ScaleTo(1.5f);
ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out);
MoveTo(EndPosition, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out);
Delay(EndTime - StartTime);
FadeOut(DrawableOsuHitObject.TIME_FADEIN);
Delay(DrawableOsuHitObject.TIME_FADEIN);
Expire(true);
}
}
}
}
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using OpenTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
@@ -80,14 +81,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
double fadeOutTime = startTime + fraction * duration;
double fadeInTime = fadeOutTime - PreEmpt;
Add(new FollowPoint
FollowPoint fp;
Add(fp = new FollowPoint
{
StartTime = fadeInTime,
EndTime = fadeOutTime,
Position = pointStartPosition,
EndPosition = pointEndPosition,
Rotation = rotation,
Alpha = 0,
Scale = new Vector2(1.5f),
});
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(DrawableOsuHitObject.TIME_FADEIN);
fp.ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out);
fp.MoveTo(pointEndPosition, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out);
fp.Delay(fadeOutTime - fadeInTime);
fp.FadeOut(DrawableOsuHitObject.TIME_FADEIN);
}
fp.Expire(true);
}
}
prevHitObject = currHitObject;
@@ -104,10 +104,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
}
protected override void UpdateState(ArmedState state)
protected override void UpdateCurrentState(ArmedState state)
{
base.UpdateState(state);
ApproachCircle.FadeOut();
double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
@@ -21,17 +21,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override OsuJudgement CreateJudgement() => new OsuJudgement { MaxScore = OsuScoreResult.Hit300 };
protected override void UpdateState(ArmedState state)
protected sealed override void UpdateState(ArmedState state)
{
Flush();
UpdateInitialState();
Delay(HitObject.StartTime - Time.Current - TIME_PREEMPT + Judgement.TimeOffset, true);
using (BeginAbsoluteSequence(HitObject.StartTime - TIME_PREEMPT, true))
{
UpdatePreemptState();
UpdatePreemptState();
using (BeginDelayedSequence(TIME_PREEMPT + Judgement.TimeOffset, true))
UpdateCurrentState(state);
}
}
Delay(TIME_PREEMPT, true);
protected virtual void UpdateCurrentState(ArmedState state)
{
}
protected virtual void UpdatePreemptState()
@@ -158,10 +158,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ball.Alpha = 0;
}
protected override void UpdateState(ArmedState state)
protected override void UpdateCurrentState(ArmedState state)
{
base.UpdateState(state);
ball.FadeIn();
Delay(slider.Duration, true);
@@ -181,4 +179,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
void UpdateProgress(double progress, int repeat);
}
}
}
@@ -72,10 +72,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Delay(-animIn);
}
protected override void UpdateState(ArmedState state)
protected override void UpdateCurrentState(ArmedState state)
{
base.UpdateState(state);
switch (state)
{
case ArmedState.Idle:
@@ -93,4 +91,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
}
}
}
@@ -132,10 +132,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
disc.FadeIn(200);
}
protected override void UpdateState(ArmedState state)
protected override void UpdateCurrentState(ArmedState state)
{
base.UpdateState(state);
Delay(spinner.Duration, true);
FadeOut(160);
+33 -12
View File
@@ -20,24 +20,33 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
private const float base_scoring_distance = 100;
public IHasCurve CurveObject { get; set; }
public SliderCurve Curve => CurveObject.Curve;
public readonly SliderCurve Curve = new SliderCurve();
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime;
public override Vector2 EndPosition => PositionAt(1);
public Vector2 PositionAt(double progress) => CurveObject.PositionAt(progress);
public double ProgressAt(double progress) => CurveObject.ProgressAt(progress);
public int RepeatAt(double progress) => CurveObject.RepeatAt(progress);
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public List<Vector2> ControlPoints => CurveObject.ControlPoints;
public CurveType CurveType => CurveObject.CurveType;
public double Distance => CurveObject.Distance;
public CurveType CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public int RepeatCount => CurveObject.RepeatCount;
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
}
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public int RepeatCount { get; set; } = 1;
private int stackHeight;
public override int StackHeight
@@ -63,6 +72,18 @@ namespace osu.Game.Rulesets.Osu.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
public IEnumerable<SliderTick> Ticks
{
get
@@ -96,12 +117,12 @@ namespace osu.Game.Rulesets.Osu.Objects
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = Samples.Select(s => new SampleInfo
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}).ToList()
}))
};
}
}
-315
View File
@@ -1,315 +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 OpenTK;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Osu
{
public class OsuAutoReplay : Replay
{
private static readonly Vector2 spinner_centre = new Vector2(256, 192);
private const float spin_radius = 50;
private readonly Beatmap<OsuHitObject> beatmap;
public OsuAutoReplay(Beatmap<OsuHitObject> beatmap)
{
this.beatmap = beatmap;
User = new User
{
Username = @"Autoplay",
};
createAutoReplay();
}
private class ReplayFrameComparer : IComparer<ReplayFrame>
{
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<ReplayFrame> replay_frame_comparer = new ReplayFrameComparer();
private int findInsertionIndex(ReplayFrame frame)
{
int index = Frames.BinarySearch(frame, replay_frame_comparer);
if (index < 0)
{
index = ~index;
}
else
{
// Go to the first index which is actually bigger
while (index < Frames.Count && frame.Time == Frames[index].Time)
{
++index;
}
}
return index;
}
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));
private double applyModsToTime(double v) => v;
private double applyModsToRate(double v) => v;
public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2);
private void createAutoReplay()
{
int buttonIndex = 0;
EasingTypes preferredEasing = DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;
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);
// Already superhuman, but still somewhat realistic
int reactionTime = (int)applyModsToRate(100);
for (int i = 0; i < beatmap.HitObjects.Count; i++)
{
OsuHitObject h = beatmap.HitObjects[i];
//if (h.EndTime < InputManager.ReplayStartTime)
//{
// h.IsHit = true;
// continue;
//}
int endDelay = h is Spinner ? 1 : 0;
if (DelayedMovements && i > 0)
{
OsuHitObject last = beatmap.HitObjects[i - 1];
double endTime = (last as IHasEndTime)?.EndTime ?? last.StartTime;
//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 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 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 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));
}
}
Vector2 targetPosition = h.Position;
EasingTypes easing = preferredEasing;
float spinnerDirection = -1;
if (h is Spinner)
{
targetPosition = Frames[Frames.Count - 1].Position;
Vector2 difference = spinner_centre - targetPosition;
float differenceLength = difference.Length;
float newLength = (float)Math.Sqrt(differenceLength * differenceLength - spin_radius * spin_radius);
if (differenceLength > spin_radius)
{
float angle = (float)Math.Asin(spin_radius / differenceLength);
if (angle > 0)
{
spinnerDirection = -1;
}
else
{
spinnerDirection = 1;
}
difference.X = difference.X * (float)Math.Cos(angle) - difference.Y * (float)Math.Sin(angle);
difference.Y = difference.X * (float)Math.Sin(angle) + difference.Y * (float)Math.Cos(angle);
difference.Normalize();
difference *= newLength;
targetPosition += difference;
easing = EasingTypes.In;
}
else if (difference.Length > 0)
{
targetPosition = spinner_centre - difference * (spin_radius / difference.Length);
}
else
{
targetPosition = spinner_centre + new Vector2(0, -spin_radius);
}
}
// Do some nice easing for cursor movements
if (Frames.Count > 0)
{
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 ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
addFrameToReplay(lastFrame);
}
Vector2 lastPosition = lastFrame.Position;
double timeDifference = applyModsToTime(h.StartTime - lastFrame.Time);
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPosition).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
{
// Perform eased movement
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 ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
}
buttonIndex = 0;
}
else
{
buttonIndex++;
}
}
ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1;
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
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;
// Do we have a previous frame? No need to check for < replay.Count since we decremented!
if (index >= 0)
{
ReplayFrame previousFrame = Frames[index];
var previousButton = previousFrame.ButtonState;
// If a button is already held, then we simply alternate
if (previousButton != ReplayButtonState.None)
{
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 = (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.
int endIndex = findInsertionIndex(endFrame);
if (index < Frames.Count - 1)
Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1)));
// After alternating we need to keep holding the other button in the future rather than the previous one.
for (int j = index + 1; j < Frames.Count; ++j)
{
// Don't affect frames which stop pressing a button!
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
Frames[j].ButtonState = button;
}
}
}
addFrameToReplay(newFrame);
// We add intermediate frames for spinning / following a slider here.
if (h is Spinner)
{
Spinner s = h as Spinner;
Vector2 difference = targetPosition - spinner_centre;
float radius = difference.Length;
float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
double t;
for (double j = h.StartTime + frameDelay; j < s.EndTime; j += frameDelay)
{
t = applyModsToTime(j - h.StartTime) * spinnerDirection;
Vector2 pos = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
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 ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
endFrame.MouseX = endPosition.X;
endFrame.MouseY = endPosition.Y;
}
else if (h is Slider)
{
Slider s = h as Slider;
for (double j = frameDelay; j < s.Duration; j += frameDelay)
{
Vector2 pos = s.PositionAt(j / s.Duration);
addFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.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!
if (Frames[Frames.Count - 1].Time <= endFrame.Time)
addFrameToReplay(endFrame);
}
//Player.currentScore.Replay = InputManager.ReplayScore.Replay;
//Player.currentScore.PlayerName = "osu!";
}
}
}
@@ -3,7 +3,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Osu
protected override void PreprocessHitObjects()
{
foreach (var h in Objects)
(h as IHasCurve)?.Curve?.Calculate();
(h as Slider)?.Curve?.Calculate();
}
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
@@ -190,4 +189,4 @@ namespace osu.Game.Rulesets.Osu
Aim,
};
}
}
}
@@ -2,10 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Screens.Play;
using OpenTK.Input;
using KeyboardState = osu.Framework.Input.KeyboardState;
@@ -17,13 +14,6 @@ namespace osu.Game.Rulesets.Osu
{
private bool leftViaKeyboard;
private bool rightViaKeyboard;
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableButtons);
}
protected override void TransformState(InputState state)
{
@@ -40,12 +30,6 @@ namespace osu.Game.Rulesets.Osu
if (mouse != null)
{
if (mouseDisabled.Value)
{
mouse.SetPressed(MouseButton.Left, false);
mouse.SetPressed(MouseButton.Right, false);
}
if (leftViaKeyboard)
mouse.SetPressed(MouseButton.Left, true);
if (rightViaKeyboard)
@@ -0,0 +1,332 @@
// 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;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using System;
using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.Replays
{
public class OsuAutoGenerator : OsuAutoGeneratorBase
{
#region Parameters
/// <summary>
/// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible.
/// Mainly for Autopilot.
/// </summary>
public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2);
#endregion
#region Constants
/// <summary>
/// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it.
/// </summary>
private readonly double reactionTime;
/// <summary>
/// What easing to use when moving between hitobjects
/// </summary>
private EasingTypes preferredEasing => DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;
#endregion
#region Construction / Initialisation
public OsuAutoGenerator(Beatmap<OsuHitObject> beatmap)
: base(beatmap)
{
// Already superhuman, but still somewhat realistic
reactionTime = ApplyModsToRate(100);
}
#endregion
#region Generator
/// <summary>
/// Which button (left or right) to use for the current hitobject.
/// Even means LMB will be used to click, odd means RMB will be used.
/// This keeps track of the button previously used for alt/singletap logic.
/// </summary>
private int buttonIndex;
public override Replay Generate()
{
buttonIndex = 0;
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));
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
OsuHitObject h = Beatmap.HitObjects[i];
if (DelayedMovements && i > 0)
{
OsuHitObject prev = Beatmap.HitObjects[i - 1];
addDelayedMovements(h, prev);
}
addHitObjectReplay(h);
}
return Replay;
}
private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
{
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
// 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 (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), prev.EndPosition.X, prev.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 (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), prev.EndPosition.X, prev.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 (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit100), prev.EndPosition.X, prev.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));
}
}
private void addHitObjectReplay(OsuHitObject h)
{
// Default values for circles/sliders
Vector2 startPosition = h.Position;
EasingTypes easing = preferredEasing;
float spinnerDirection = -1;
// The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position
// We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition.
// TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner)
{
calcSpinnerStartPosAndDirection(Frames[Frames.Count - 1].Position, out startPosition, out spinnerDirection);
Vector2 spinCentreOffset = SPINNER_CENTRE - Frames[Frames.Count - 1].Position;
if (spinCentreOffset.Length > SPIN_RADIUS)
{
// If moving in from the outside, don't ease out (default eases out). This means auto will "start" spinning immediately after moving into position.
easing = EasingTypes.In;
}
}
// Do some nice easing for cursor movements
if (Frames.Count > 0)
{
moveToHitObject(h.StartTime, startPosition, h.Radius, easing);
}
// Add frames to click the hitobject
addHitObjectClickFrames(h, startPosition, spinnerDirection);
}
#endregion
#region Helper subroutines
private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection)
{
Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos;
float distFromCentre = spinCentreOffset.Length;
float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
if (distFromCentre > SPIN_RADIUS)
{
// Previous cursor position was outside spin circle, set startPosition to the tangent point.
// Angle between centre offset and tangent point offset.
float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre);
if (angle > 0)
{
spinnerDirection = -1;
}
else
{
spinnerDirection = 1;
}
// Rotate by angle so it's parallel to tangent line
spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle);
spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle);
// Set length to distToTangentPoint
spinCentreOffset.Normalize();
spinCentreOffset *= distToTangentPoint;
// Move along the tangent line, now startPosition is at the tangent point.
startPosition = prevPos + spinCentreOffset;
}
else if (spinCentreOffset.Length > 0)
{
// Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle.
startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length);
spinnerDirection = 1;
}
else
{
// Degenerate case where cursor position is exactly at the centre of the spin circle.
startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS);
spinnerDirection = 1;
}
}
private void moveToHitObject(double targetTime, Vector2 targetPos, double hitObjectRadius, EasingTypes easing)
{
ReplayFrame lastFrame = Frames[Frames.Count - 1];
// Wait until Auto could "see and react" to the next note.
double waitTime = targetTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime);
if (waitTime > lastFrame.Time)
{
lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
AddFrameToReplay(lastFrame);
}
Vector2 lastPosition = lastFrame.Position;
double timeDifference = ApplyModsToTime(targetTime - lastFrame.Time);
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPos).Length > hitObjectRadius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
{
// Perform eased movement
for (double time = lastFrame.Time + FrameDelay; time < targetTime; time += FrameDelay)
{
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, targetTime, easing);
AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
}
buttonIndex = 0;
}
else
{
buttonIndex++;
}
}
// Add frames to click the hitobject
private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection)
{
// Time to insert the first frame which clicks the object
// Here we mainly need to determine which button to use
ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1;
ReplayFrame startFrame = new ReplayFrame(h.StartTime, startPosition.X, startPosition.Y, button);
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
int endDelay = h is Spinner ? 1 : 0;
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(startFrame) - 1;
// If the previous frame has a button pressed, force alternation.
// If there are frames ahead, modify those to use the new button press.
// Do we have a previous frame? No need to check for < replay.Count since we decremented!
if (index >= 0)
{
ReplayFrame previousFrame = Frames[index];
var previousButton = previousFrame.ButtonState;
// If a button is already held, then we simply alternate
if (previousButton != ReplayButtonState.None)
{
Debug.Assert(previousButton != (ReplayButtonState.Left1 | ReplayButtonState.Right1), "Previous button state was not Left1 nor Right1 despite only using those two states.");
// Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
if (previousButton == button)
{
button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button;
startFrame.ButtonState = button;
}
// We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
int endIndex = FindInsertionIndex(endFrame);
if (index < Frames.Count - 1)
Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1)));
// After alternating we need to keep holding the other button in the future rather than the previous one.
for (int j = index + 1; j < Frames.Count; ++j)
{
// Don't affect frames which stop pressing a button!
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
Frames[j].ButtonState = button;
}
}
}
AddFrameToReplay(startFrame);
// We add intermediate frames for spinning / following a slider here.
if (h is Spinner)
{
Spinner s = h as Spinner;
Vector2 difference = startPosition - SPINNER_CENTRE;
float radius = difference.Length;
float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
double t;
for (double j = h.StartTime + FrameDelay; j < s.EndTime; j += FrameDelay)
{
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
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 ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
endFrame.MouseX = endPosition.X;
endFrame.MouseY = endPosition.Y;
}
else if (h is Slider)
{
Slider s = h as Slider;
for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
{
Vector2 pos = s.PositionAt(j / s.Duration);
AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.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!
if (Frames[Frames.Count - 1].Time <= endFrame.Time)
AddFrameToReplay(endFrame);
}
#endregion
}
}
@@ -0,0 +1,96 @@
// 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;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Osu.Replays
{
public abstract class OsuAutoGeneratorBase : AutoGenerator<OsuHitObject>
{
#region Constants
/// <summary>
/// Constants (for spinners).
/// </summary>
protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192);
protected const float SPIN_RADIUS = 50;
/// <summary>
/// The time in ms between each ReplayFrame.
/// </summary>
protected readonly double FrameDelay;
#endregion
#region Construction / Initialisation
protected Replay Replay;
protected List<ReplayFrame> Frames => Replay.Frames;
protected OsuAutoGeneratorBase(Beatmap<OsuHitObject> beatmap)
: base(beatmap)
{
Replay = new Replay
{
User = new User
{
Username = @"Autoplay",
}
};
// 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.
FrameDelay = ApplyModsToRate(1000.0 / 60.0);
}
#endregion
#region Utilities
protected double ApplyModsToTime(double v) => v;
protected double ApplyModsToRate(double v) => v;
private class ReplayFrameComparer : IComparer<ReplayFrame>
{
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<ReplayFrame> replay_frame_comparer = new ReplayFrameComparer();
protected int FindInsertionIndex(ReplayFrame frame)
{
int index = Frames.BinarySearch(frame, replay_frame_comparer);
if (index < 0)
{
index = ~index;
}
else
{
// Go to the first index which is actually bigger
while (index < Frames.Count && frame.Time == Frames[index].Time)
{
++index;
}
}
return index;
}
protected void AddFrameToReplay(ReplayFrame frame) => Frames.Insert(FindInsertionIndex(frame), frame);
protected static Vector2 CirclePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius));
#endregion
}
}
@@ -69,7 +69,6 @@
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuAutoReplay.cs" />
<Compile Include="OsuDifficultyCalculator.cs" />
<Compile Include="OsuKeyConversionInputManager.cs" />
<Compile Include="Scoring\OsuScoreProcessor.cs" />
@@ -83,6 +82,8 @@
<Compile Include="Objects\Spinner.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Mods\OsuMod.cs" />
<Compile Include="Replays\OsuAutoGenerator.cs" />
<Compile Include="Replays\OsuAutoGeneratorBase.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
@@ -102,6 +103,9 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Replays\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
@@ -66,9 +66,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var distanceData = obj as IHasDistance;
var repeatsData = obj as IHasRepeats;
var endTimeData = obj as IHasEndTime;
var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information
List<SampleInfo> samples = obj.Samples;
SampleInfoList samples = obj.Samples;
bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
@@ -102,16 +103,35 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
List<SampleInfoList> allSamples = curveData != null ? curveData.RepeatSamples : new List<SampleInfoList>(new[] { samples });
int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
// Todo: This should generate different type of hits (including strongs)
// depending on hitobject sound additions (not implemented fully yet)
yield return new CentreHit
SampleInfoList currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH);
if (isRim)
{
StartTime = j,
Samples = obj.Samples,
IsStrong = strong,
};
yield return new RimHit
{
StartTime = j,
Samples = currentSamples,
IsStrong = strong
};
}
else
{
yield return new CentreHit
{
StartTime = j,
Samples = currentSamples,
IsStrong = strong,
};
}
i = (i + 1) % allSamples.Count;
}
}
else
+1 -1
View File
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score
{
User = new User { Username = "mekkadosu!" },
Replay = new TaikoAutoReplay(beatmap)
Replay = new TaikoAutoReplay(beatmap).Generate(),
};
}
}
@@ -148,9 +148,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
var completion = (float)userHits / HitObject.RequiredHits;
expandingRing.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50);
expandingRing.Delay(50);
expandingRing.FadeTo(completion / 8, 2000, EasingTypes.OutQuint);
expandingRing.DelayReset();
using (expandingRing.BeginDelayedSequence(50))
expandingRing.FadeTo(completion / 8, 2000, EasingTypes.OutQuint);
symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, EasingTypes.OutQuint);
+2 -2
View File
@@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
TickSpacing = tickSpacing,
StartTime = t,
IsStrong = IsStrong,
Samples = Samples.Select(s => new SampleInfo
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}).ToList()
}))
});
first = false;
@@ -0,0 +1,127 @@
// 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.Rulesets.Taiko.Objects
{
internal class TaikoHitObjectDifficulty
{
/// <summary>
/// Factor by how much individual / overall strain decays per second.
/// </summary>
/// <remarks>
/// These values are results of tweaking a lot and taking into account general feedback.
/// </remarks>
internal const double DECAY_BASE = 0.30;
private const double type_change_bonus = 0.75;
private const double rhythm_change_bonus = 1.0;
private const double rhythm_change_base_threshold = 0.2;
private const double rhythm_change_base = 2.0;
internal TaikoHitObject BaseHitObject;
/// <summary>
/// Measures note density in a way
/// </summary>
internal double Strain = 1;
private double timeElapsed;
private int sameTypeSince = 1;
private bool isRim => BaseHitObject is RimHit;
public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject)
{
BaseHitObject = baseHitObject;
}
internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate)
{
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
// See Taiko feedback thread.
timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
double addition = 1;
// Only if we are no slider or spinner we get an extra addition
if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit
&& BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past
{
addition += typeChangeAddition(previousHitObject);
addition += rhythmChangeAddition(previousHitObject);
}
double additionFactor = 1.0;
// Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50
if (timeElapsed < 50.0)
additionFactor = 0.4 + 0.6 * timeElapsed / 50.0;
Strain = previousHitObject.Strain * decay + addition * additionFactor;
}
private TypeSwitch lastTypeSwitchEven = TypeSwitch.None;
private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject)
{
// If we don't have the same hit type, trigger a type change!
if (previousHitObject.isRim != isRim)
{
lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd;
// We only want a bonus if the parity of the type switch changes!
switch (previousHitObject.lastTypeSwitchEven)
{
case TypeSwitch.Even:
if (lastTypeSwitchEven == TypeSwitch.Odd)
return type_change_bonus;
break;
case TypeSwitch.Odd:
if (lastTypeSwitchEven == TypeSwitch.Even)
return type_change_bonus;
break;
}
}
// No type change? Increment counter and keep track of last type switch
else
{
lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven;
sameTypeSince = previousHitObject.sameTypeSince + 1;
}
return 0;
}
private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject)
{
// We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time.
if (timeElapsed == 0 || previousHitObject.timeElapsed == 0)
return 0;
double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed);
if (timeElapsedRatio >= 8)
return 0;
double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
if (isWithinChangeThreshold(difference))
return rhythm_change_bonus;
return 0;
}
private bool isWithinChangeThreshold(double value)
{
return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold;
}
private enum TypeSwitch
{
None,
Even,
Odd
}
}
}
@@ -1,7 +1,8 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
@@ -9,27 +10,28 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Taiko.Replays
{
public class TaikoAutoReplay : Replay
public class TaikoAutoReplay : AutoGenerator<TaikoHitObject>
{
private readonly Beatmap<TaikoHitObject> beatmap;
private const double swell_hit_speed = 50;
public TaikoAutoReplay(Beatmap<TaikoHitObject> beatmap)
: base(beatmap)
{
this.beatmap = beatmap;
createAutoReplay();
}
private void createAutoReplay()
protected Replay Replay;
protected List<ReplayFrame> Frames => Replay.Frames;
public override Replay Generate()
{
bool hitButton = true;
Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None));
Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None));
for (int i = 0; i < beatmap.HitObjects.Count; i++)
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
TaikoHitObject h = beatmap.HitObjects[i];
TaikoHitObject h = Beatmap.HitObjects[i];
ReplayButtonState button;
@@ -45,12 +47,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
int d = 0;
int count = 0;
int req = swell.RequiredHits;
double hitRate = swell.Duration / req;
double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
for (double j = h.StartTime; j < endTime; j += hitRate)
{
switch (d)
{
default:
case 0:
button = ReplayButtonState.Left1;
break;
case 1:
@@ -66,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
Frames.Add(new ReplayFrame(j, null, null, button));
d = (d + 1) % 4;
if (++count > req)
if (++count == req)
break;
}
}
@@ -102,15 +105,17 @@ namespace osu.Game.Rulesets.Taiko.Replays
Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None));
if (i < beatmap.HitObjects.Count - 1)
if (i < Beatmap.HitObjects.Count - 1)
{
double waitTime = beatmap.HitObjects[i + 1].StartTime - 1000;
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime)
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None));
}
hitButton = !hitButton;
}
return Replay;
}
}
}
@@ -6,18 +6,133 @@ using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using System.Collections.Generic;
using System.Globalization;
using System;
namespace osu.Game.Rulesets.Taiko
{
public class TaikoDifficultyCalculator : DifficultyCalculator<TaikoHitObject>
internal class TaikoDifficultyCalculator : DifficultyCalculator<TaikoHitObject>
{
public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap)
private const double star_scaling_factor = 0.04125;
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
private const double strain_step = 400;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
private const double decay_weight = 0.9;
/// <summary>
/// HitObjects are stored as a member variable.
/// </summary>
private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
public TaikoDifficultyCalculator(Beatmap beatmap)
: base(beatmap)
{
}
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)
{
return 0;
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
foreach (var hitObject in Objects)
difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues()) return 0;
double starRating = calculateDifficulty() * star_scaling_factor;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture));
categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture));
}
return starRating;
}
private bool calculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<TaikoHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
{
if (!hitObjectsEnumerator.MoveNext()) return false;
TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current;
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
while (hitObjectsEnumerator.MoveNext())
{
var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate);
current = next;
}
return true;
}
}
private double calculateDifficulty()
{
double actualStrainStep = strain_step * TimeRate;
// Find the highest strain value within each strain step
List<double> highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
TaikoHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strain * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= decay_weight;
}
return difficulty;
}
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
@@ -81,6 +81,7 @@
<Compile Include="Replays\TaikoFramedReplayInputHandler.cs" />
<Compile Include="Replays\TaikoAutoReplay.cs" />
<Compile Include="Objects\TaikoHitObject.cs" />
<Compile Include="Objects\TaikoHitObjectDifficulty.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
+19
View File
@@ -0,0 +1,19 @@
// 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.Audio
{
public class SampleInfoList : List<SampleInfo>
{
public SampleInfoList()
{
}
public SampleInfoList(IEnumerable<SampleInfo> elements)
{
AddRange(elements);
}
}
}
@@ -35,6 +35,10 @@ namespace osu.Game.Beatmaps
protected DifficultyCalculator(Beatmap beatmap)
{
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects)
h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);
PreprocessHitObjects();
}
+2 -4
View File
@@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Drawables
/// <summary>
/// Fires when one of our difficulties was selected. Will fire on first expand.
/// </summary>
public Action<BeatmapGroup, BeatmapInfo> SelectionChanged;
public Action<BeatmapGroup, BeatmapPanel> SelectionChanged;
/// <summary>
/// Fires when one of our difficulties is clicked when already selected. Should start playing the map.
@@ -89,8 +89,6 @@ namespace osu.Game.Beatmaps.Drawables
//we want to make sure one of our children is selected in the case none have been selected yet.
if (SelectedPanel == null)
BeatmapPanels.First().State = PanelSelectedState.Selected;
else
SelectionChanged?.Invoke(this, SelectedPanel.Beatmap);
}
private void panelGainedSelection(BeatmapPanel panel)
@@ -106,7 +104,7 @@ namespace osu.Game.Beatmaps.Drawables
finally
{
State = BeatmapGroupState.Expanded;
SelectionChanged?.Invoke(this, panel.Beatmap);
SelectionChanged?.Invoke(this, SelectedPanel);
}
}
}
@@ -1,19 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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 OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
@@ -23,8 +22,6 @@ namespace osu.Game.Beatmaps.Drawables
private readonly SpriteText title;
private readonly SpriteText artist;
private Bindable<bool> preferUnicode;
private readonly WorkingBeatmap beatmap;
private readonly FillFlowContainer difficultyIcons;
@@ -82,15 +79,10 @@ namespace osu.Game.Beatmaps.Drawables
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load(LocalisationEngine localisation)
{
preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode);
preferUnicode.ValueChanged += unicode =>
{
title.Text = unicode ? beatmap.BeatmapSetInfo.Metadata.TitleUnicode : beatmap.BeatmapSetInfo.Metadata.Title;
artist.Text = unicode ? beatmap.BeatmapSetInfo.Metadata.ArtistUnicode : beatmap.BeatmapSetInfo.Metadata.Artist;
};
preferUnicode.TriggerChange();
title.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.TitleUnicode, beatmap.BeatmapSetInfo.Metadata.Title);
artist.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.ArtistUnicode, beatmap.BeatmapSetInfo.Metadata.Artist);
}
private class PanelBackground : BufferedContainer
@@ -112,7 +104,7 @@ namespace osu.Game.Beatmaps.Drawables
Depth = -1,
Direction = FillDirection.Horizontal,
RelativeSizeAxes = Axes.Both,
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
Shear = new Vector2(0.8f, 0),
Alpha = 0.5f,
Children = new[]
@@ -0,0 +1,69 @@
// 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.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Graphics;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
internal class DifficultyColouredContainer : Container, IHasAccentColour
{
public Color4 AccentColour { get; set; }
private readonly BeatmapInfo beatmap;
private OsuColour palette;
public DifficultyColouredContainer(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load(OsuColour palette)
{
this.palette = palette;
AccentColour = getColour(beatmap);
}
private enum DifficultyRating
{
Easy,
Normal,
Hard,
Insane,
Expert
}
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
{
var rating = beatmap.StarDifficulty;
if (rating < 1.5) return DifficultyRating.Easy;
if (rating < 2.25) return DifficultyRating.Normal;
if (rating < 3.75) return DifficultyRating.Hard;
if (rating < 5.25) return DifficultyRating.Insane;
return DifficultyRating.Expert;
}
private Color4 getColour(BeatmapInfo beatmap)
{
switch (getDifficultyRating(beatmap))
{
case DifficultyRating.Easy:
return palette.Green;
default:
case DifficultyRating.Normal:
return palette.Yellow;
case DifficultyRating.Hard:
return palette.Pink;
case DifficultyRating.Insane:
return palette.Purple;
case DifficultyRating.Expert:
return palette.Gray0;
}
}
}
}
+7 -49
View File
@@ -3,7 +3,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Graphics;
using OpenTK;
@@ -11,23 +10,20 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
internal class DifficultyIcon : Container
internal class DifficultyIcon : DifficultyColouredContainer
{
private readonly BeatmapInfo beatmap;
private OsuColour palette;
public DifficultyIcon(BeatmapInfo beatmap)
public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap)
{
this.beatmap = beatmap;
const float size = 20;
Size = new Vector2(size);
Size = new Vector2(20);
}
[BackgroundDependencyLoader]
private void load(OsuColour palette)
private void load()
{
this.palette = palette;
Children = new[]
{
new TextAwesome
@@ -35,7 +31,7 @@ namespace osu.Game.Beatmaps.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = Size.X,
Colour = getColour(beatmap),
Colour = AccentColour,
Icon = FontAwesome.fa_circle
},
new TextAwesome
@@ -48,43 +44,5 @@ namespace osu.Game.Beatmaps.Drawables
}
};
}
private enum DifficultyRating
{
Easy,
Normal,
Hard,
Insane,
Expert
}
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
{
var rating = beatmap.StarDifficulty;
if (rating < 1.5) return DifficultyRating.Easy;
if (rating < 2.25) return DifficultyRating.Normal;
if (rating < 3.75) return DifficultyRating.Hard;
if (rating < 5.25) return DifficultyRating.Insane;
return DifficultyRating.Expert;
}
private Color4 getColour(BeatmapInfo beatmap)
{
switch (getDifficultyRating(beatmap))
{
case DifficultyRating.Easy:
return palette.Green;
default:
case DifficultyRating.Normal:
return palette.Yellow;
case DifficultyRating.Hard:
return palette.Pink;
case DifficultyRating.Insane:
return palette.Purple;
case DifficultyRating.Expert:
return palette.Gray0;
}
}
}
}
}
@@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Formats
// TODO: Not sure how far back to go, or differences between versions
}
private HitObjectParser parser;
private ConvertHitObjectParser parser;
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
@@ -90,16 +90,16 @@ namespace osu.Game.Beatmaps.Formats
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
parser = new Rulesets.Objects.Legacy.Osu.HitObjectParser();
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
break;
case 1:
parser = new Rulesets.Objects.Legacy.Taiko.HitObjectParser();
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
break;
case 2:
parser = new Rulesets.Objects.Legacy.Catch.HitObjectParser();
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
break;
case 3:
parser = new Rulesets.Objects.Legacy.Mania.HitObjectParser();
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
break;
}
break;
+4 -1
View File
@@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps
public readonly BeatmapSetInfo BeatmapSetInfo;
public readonly Bindable<IEnumerable<Mod>> Mods = new Bindable<IEnumerable<Mod>>();
public readonly Bindable<IEnumerable<Mod>> Mods = new Bindable<IEnumerable<Mod>>(new Mod[] { });
public readonly bool WithStoryboard;
@@ -94,6 +94,9 @@ namespace osu.Game.Beatmaps
{
if (track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
other.track = track;
if (background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
other.background = background;
}
public virtual void Dispose()
+9 -18
View File
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Configuration;
using osu.Framework.Platform;
using osu.Game.Screens.Select;
namespace osu.Game.Configuration
{
@@ -24,7 +25,7 @@ namespace osu.Game.Configuration
Set(OsuConfig.MenuCursorSize, 1.0, 0.5f, 2);
Set(OsuConfig.GameplayCursorSize, 1.0, 0.5f, 2);
Set(OsuConfig.DimLevel, 30, 0, 100);
Set(OsuConfig.DimLevel, 0.3, 0, 1);
Set(OsuConfig.MouseDisableButtons, false);
Set(OsuConfig.MouseDisableWheel, false);
@@ -34,10 +35,15 @@ namespace osu.Game.Configuration
Set(OsuConfig.MenuParallax, true);
Set(OsuConfig.MenuVoice, true);
Set(OsuConfig.MenuMusic, true);
Set(OsuConfig.BeatmapDetailTab, BeatmapDetailTab.Details);
Set(OsuConfig.ShowInterface, true);
Set(OsuConfig.KeyOverlay, false);
//todo: implement all settings below this line (remove the Disabled set when doing so).
//todo: implement all settings below this line (remove the Disabled set when doing so).
Set(OsuConfig.AudioOffset, 0, -500.0, 500.0);
Set(OsuConfig.MouseSpeed, 1.0).Disabled = true;
@@ -86,7 +92,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.IgnoreBeatmapSamples, false).Disabled = true;
Set(OsuConfig.IgnoreBeatmapSkins, false).Disabled = true;
Set(OsuConfig.IgnoreList, string.Empty).Disabled = true;
Set(OsuConfig.Language, @"unknown").Disabled = true;
Set(OsuConfig.AllowNowPlayingHighlights, false).Disabled = true;
Set(OsuConfig.LastVersion, string.Empty).Disabled = true;
Set(OsuConfig.LastVersionPermissionsFailed, string.Empty).Disabled = true;
@@ -145,8 +150,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.YahooIntegration, false).Disabled = true;
Set(OsuConfig.ForceFrameFlush, false).Disabled = true;
Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true;
Set(OsuConfig.MenuMusic, true).Disabled = true;
Set(OsuConfig.MenuVoice, true).Disabled = true;
Set(OsuConfig.RawInput, false).Disabled = true;
Set(OsuConfig.AbsoluteToOsuWindow, Get<bool>(OsuConfig.RawInput)).Disabled = true;
Set(OsuConfig.ShowMenuTips, true).Disabled = true;
@@ -159,16 +162,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.UpdateFailCount, 0).Disabled = true;
//Set(OsuConfig.TreeSortMode, TreeGroupMode.Show_All).Disabled = true;
//Set(OsuConfig.TreeSortMode2, TreeSortMode.Title).Disabled = true;
bool unicodeDefault = false;
switch (Get<string>(OsuConfig.Language))
{
case @"zh":
case @"ja":
case @"ko":
unicodeDefault = true;
break;
}
Set(OsuConfig.ShowUnicode, unicodeDefault);
Set(OsuConfig.PermanentSongInfo, false).Disabled = true;
Set(OsuConfig.Ticker, false).Disabled = true;
Set(OsuConfig.CompatibilityContext, false).Disabled = true;
@@ -176,7 +169,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.ConfineMouse, Get<bool>(OsuConfig.ConfineMouseToFullscreen) ?
ConfineMouseMode.Fullscreen : ConfineMouseMode.Never).Disabled = true;
GetOriginalBindable<bool>(OsuConfig.SavePassword).ValueChanged += delegate
{
if (Get<bool>(OsuConfig.SavePassword)) Set(OsuConfig.SaveUsername, true);
@@ -316,6 +308,7 @@ namespace osu.Game.Configuration
MenuMusic,
MenuVoice,
MenuParallax,
BeatmapDetailTab,
RawInput,
AbsoluteToOsuWindow,
ConfineMouse,
@@ -335,11 +328,9 @@ namespace osu.Game.Configuration
SaveUsername,
TreeSortMode,
TreeSortMode2,
ShowUnicode,
PermanentSongInfo,
Ticker,
CompatibilityContext,
CanForceOptimusCompatibility,
}
}
+4
View File
@@ -93,5 +93,9 @@ namespace osu.Game.Database
public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Path == other.BeatmapSet.Path &&
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Path == other.BeatmapSet.Path &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
}
}
+3
View File
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
namespace osu.Game.Database
{
@@ -18,11 +19,13 @@ namespace osu.Game.Database
/// <summary>
/// Points of failure on a relative time scale (usually 0..100).
/// </summary>
[JsonProperty(@"fail")]
public IEnumerable<int> Fails { get; set; }
/// <summary>
/// Points of retry on a relative time scale (usually 0..100).
/// </summary>
[JsonProperty(@"exit")]
public IEnumerable<int> Retries { get; set; }
}
}
+15 -13
View File
@@ -123,6 +123,8 @@ namespace osu.Game.Graphics.UserInterface
set { label.Text = value; }
}
protected readonly TextAwesome Icon;
private Color4? accentColour;
public virtual Color4 AccentColour
{
@@ -145,19 +147,19 @@ namespace osu.Game.Graphics.UserInterface
Foreground.Children = new Drawable[]
{
label = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new TextAwesome
{
Icon = FontAwesome.fa_chevron_down,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 4 },
TextSize = 20
}
label = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
Icon = new TextAwesome
{
Icon = FontAwesome.fa_chevron_down,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 4 },
TextSize = 20
}
};
}
@@ -23,14 +23,14 @@ namespace osu.Game.Graphics.UserInterface
private readonly Box leftBox;
private readonly Box rightBox;
public string TooltipText
public virtual string TooltipText
{
get
{
var bindableDouble = CurrentNumber as BindableNumber<double>;
if (bindableDouble != null)
{
if (bindableDouble.MaxValue == 1 && bindableDouble.MinValue == 0)
if (bindableDouble.MaxValue == 1 && (bindableDouble.MinValue == 0 || bindableDouble.MinValue == -1))
return bindableDouble.Value.ToString(@"P0");
return bindableDouble.Value.ToString(@"n1");
}
@@ -131,14 +131,14 @@ namespace osu.Game.Graphics.UserInterface
foreach (var star in stars.Children)
{
star.ClearTransforms(true);
if (count <= newValue)
star.Delay(Math.Max(i - count, 0) * animationDelay, true);
else
star.Delay(Math.Max(count - 1 - i, 0) * animationDelay, true);
star.FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration);
star.Icon.ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing);
star.DelayReset();
var delay = (count <= newValue ? Math.Max(i - count, 0) : Math.Max(count - 1 - i, 0)) * animationDelay;
using (BeginDelayedSequence(delay, true))
{
star.FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration);
star.Icon.ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing);
}
i++;
}
@@ -0,0 +1,46 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Database;
namespace osu.Game.Online.API.Requests
{
public class GetBeatmapDetailsRequest : APIRequest<GetBeatmapDetailsResponse>
{
private readonly BeatmapInfo beatmap;
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}";
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
}
protected override string Target => $@"beatmaps/{lookupString}";
}
public class GetBeatmapDetailsResponse : BeatmapMetrics
{
//the online API returns some metrics as a nested object.
[JsonProperty(@"failtimes")]
private BeatmapMetrics failTimes
{
set
{
Fails = value.Fails;
Retries = value.Retries;
}
}
//and other metrics in the beatmap set.
[JsonProperty(@"beatmapset")]
private BeatmapMetrics beatmapSet
{
set
{
Ratings = value.Ratings;
}
}
}
}
+1
View File
@@ -149,6 +149,7 @@ namespace osu.Game
{
new Container
{
AlwaysReceiveInput = true,
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue,
Children = new Drawable[]
+137
View File
@@ -0,0 +1,137 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Select;
using OpenTK;
using OpenTK.Graphics;
using System;
namespace osu.Game.Overlays.Music
{
internal class FilterControl : Container
{
public readonly FilterTextBox Search;
public FilterControl()
{
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, 10f),
Children = new Drawable[]
{
Search = new FilterTextBox
{
RelativeSizeAxes = Axes.X,
Height = 40,
Exit = () => ExitRequested?.Invoke(),
},
new CollectionsDropdown<PlaylistCollection>
{
RelativeSizeAxes = Axes.X,
Items = new[] { new KeyValuePair<string, PlaylistCollection>(@"All", PlaylistCollection.All) },
}
},
},
};
Search.Current.ValueChanged += current_ValueChanged;
}
private void current_ValueChanged(string newValue) => FilterChanged?.Invoke(newValue);
public Action ExitRequested;
public Action<string> FilterChanged;
public class FilterTextBox : SearchTextBox
{
private Color4 backgroundColour;
protected override Color4 BackgroundUnfocused => backgroundColour;
protected override Color4 BackgroundFocused => backgroundColour;
public FilterTextBox()
{
Masking = true;
CornerRadius = 5;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
backgroundColour = colours.Gray2;
}
}
private class CollectionsDropdown<T> : OsuDropdown<T>
{
protected override DropdownHeader CreateHeader() => new CollectionsHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new CollectionsMenu();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Gray6;
}
private class CollectionsHeader : OsuDropdownHeader
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray4;
}
public CollectionsHeader()
{
CornerRadius = 5;
Height = 30;
Icon.TextSize = 14;
Icon.Margin = new MarginPadding(0);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 };
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
private class CollectionsMenu : OsuMenu
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray4;
}
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
}
}
}
+119
View File
@@ -0,0 +1,119 @@
// 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.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Music
{
internal class PlaylistItem : Container
{
private const float fade_duration = 100;
private Color4 hoverColour;
private TextAwesome handle;
private OsuSpriteText title;
public readonly BeatmapSetInfo BeatmapSetInfo;
public Action<BeatmapSetInfo> OnSelect;
private bool selected;
public bool Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
Flush(true);
title.FadeColour(Selected ? hoverColour : Color4.White, fade_duration);
}
}
public PlaylistItem(BeatmapSetInfo setInfo)
{
BeatmapSetInfo = setInfo;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Top = 3, Bottom = 3 };
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation)
{
BeatmapMetadata metadata = BeatmapSetInfo.Metadata;
Children = new Drawable[]
{
handle = new TextAwesome
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
TextSize = 12,
Colour = colours.Gray5,
Icon = FontAwesome.fa_bars,
Alpha = 0f,
Margin = new MarginPadding { Left = 5 },
Padding = new MarginPadding { Top = 2 },
},
new FillFlowContainer<OsuSpriteText>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = 20 },
Spacing = new Vector2(5f, 0f),
Children = new []
{
title = new OsuSpriteText
{
TextSize = 16,
Font = @"Exo2.0-Regular",
Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title),
},
new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-Bold",
Colour = colours.Gray9,
Padding = new MarginPadding { Top = 1 },
Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist),
}
}
},
};
hoverColour = colours.Yellow;
}
protected override bool OnHover(Framework.Input.InputState state)
{
handle.FadeIn(fade_duration);
return base.OnHover(state);
}
protected override void OnHoverLost(Framework.Input.InputState state)
{
handle.FadeOut(fade_duration);
}
protected override bool OnClick(Framework.Input.InputState state)
{
OnSelect?.Invoke(BeatmapSetInfo);
return true;
}
}
}
+61
View File
@@ -0,0 +1,61 @@
// 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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
namespace osu.Game.Overlays.Music
{
internal class PlaylistList : Container
{
private readonly FillFlowContainer<PlaylistItem> items;
public IEnumerable<BeatmapSetInfo> BeatmapSets
{
set
{
items.Children = value.Select(item => new PlaylistItem(item) { OnSelect = itemSelected }).ToList();
}
}
private void itemSelected(BeatmapSetInfo b)
{
OnSelect?.Invoke(b);
}
public Action<BeatmapSetInfo> OnSelect;
public BeatmapSetInfo SelectedItem
{
get { return items.Children.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
set
{
foreach (PlaylistItem s in items.Children)
s.Selected = s.BeatmapSetInfo.ID == value?.ID;
}
}
public PlaylistList()
{
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
items = new FillFlowContainer<PlaylistItem>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
},
},
};
}
}
}
+169
View File
@@ -0,0 +1,169 @@
// 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;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions;
namespace osu.Game.Overlays.Music
{
public class PlaylistOverlay : OverlayContainer
{
private const float transition_duration = 600;
private const float playlist_height = 510;
private FilterControl filter;
private PlaylistList list;
private TrackManager trackManager;
private BeatmapDatabase beatmaps;
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
protected IEnumerable<BeatmapSetInfo> BeatmapSets;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapDatabase beatmaps, OsuColour colours)
{
this.beatmaps = beatmaps;
trackManager = game.Audio.Track;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 5,
Masking = true,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new Drawable[]
{
new Box
{
Colour = colours.Gray3,
RelativeSizeAxes = Axes.Both,
},
list = new PlaylistList
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
OnSelect = itemSelected,
},
filter = new FilterControl
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ExitRequested = () => State = Visibility.Hidden,
FilterChanged = filterChanged,
Padding = new MarginPadding(10),
},
},
},
};
list.BeatmapSets = BeatmapSets = beatmaps.GetAllWithChildren<BeatmapSetInfo>().ToList();
beatmapBacking.BindTo(game.Beatmap);
}
private void filterChanged(string newValue)
{
// TODO: implement
}
protected override void LoadComplete()
{
base.LoadComplete();
beatmapBacking.ValueChanged += b => list.SelectedItem = b?.BeatmapSetInfo;
beatmapBacking.TriggerChange();
}
protected override void PopIn()
{
filter.Search.HoldFocus = true;
Schedule(() => filter.Search.TriggerFocus());
ResizeTo(new Vector2(1, playlist_height), transition_duration, EasingTypes.OutQuint);
FadeIn(transition_duration, EasingTypes.OutQuint);
}
protected override void PopOut()
{
filter.Search.HoldFocus = false;
ResizeTo(new Vector2(1, 0), transition_duration, EasingTypes.OutQuint);
FadeOut(transition_duration);
}
private void itemSelected(BeatmapSetInfo set)
{
if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1))
{
beatmapBacking.Value?.Track?.Seek(0);
return;
}
playSpecified(set.Beatmaps[0]);
}
public void PlayPrevious()
{
var currentID = beatmapBacking.Value?.BeatmapSetInfo.ID ?? -1;
var available = BeatmapSets.Reverse();
var playable = available.SkipWhile(b => b.ID != currentID).Skip(1).FirstOrDefault() ?? available.FirstOrDefault();
if (playable != null)
playSpecified(playable.Beatmaps[0]);
}
public void PlayNext()
{
var currentID = beatmapBacking.Value?.BeatmapSetInfo.ID ?? -1;
var available = BeatmapSets;
var playable = available.SkipWhile(b => b.ID != currentID).Skip(1).FirstOrDefault() ?? available.FirstOrDefault();
if (playable != null)
playSpecified(playable.Beatmaps[0]);
}
private void playSpecified(BeatmapInfo info)
{
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
Task.Run(() =>
{
var track = beatmapBacking.Value.Track;
trackManager.SetExclusive(track);
track.Start();
}).ContinueWith(task => Schedule(task.ThrowIfFaulted), TaskContinuationOptions.OnlyOnFaulted);
}
}
//todo: placeholder
public enum PlaylistCollection
{
All
}
}
+173 -190
View File
@@ -2,59 +2,59 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Threading;
using osu.Game.Overlays.Music;
namespace osu.Game.Overlays
{
public class MusicController : FocusedOverlayContainer
{
private Drawable currentBackground;
private DragBar progress;
private Button playButton;
private SpriteText title, artist;
private const float player_height = 130;
private List<BeatmapSetInfo> playList;
private readonly List<BeatmapInfo> playHistory = new List<BeatmapInfo>();
private int playListIndex;
private int playHistoryIndex = -1;
private TrackManager trackManager;
private Bindable<WorkingBeatmap> beatmapSource;
private Bindable<bool> preferUnicode;
private WorkingBeatmap current;
private BeatmapDatabase beatmaps;
private Container dragContainer;
private const float transition_length = 800;
private const float progress_height = 10;
private const float bottom_black_area_height = 55;
private Drawable currentBackground;
private DragBar progressBar;
private Button playButton;
private Button playlistButton;
private SpriteText title, artist;
private PlaylistOverlay playlist;
private LocalisationEngine localisation;
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
private Container dragContainer;
private Container playerContainer;
public MusicController()
{
Width = 400;
Height = 130;
Margin = new MarginPadding(10);
}
@@ -80,247 +80,227 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game, OsuConfigManager config, BeatmapDatabase beatmaps, OsuColour colours)
private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation)
{
this.localisation = localisation;
Children = new Drawable[]
{
dragContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
title = new OsuSpriteText
playlist = new PlaylistOverlay
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 40),
TextSize = 25,
Colour = Color4.White,
Text = @"Nothing to play",
Font = @"Exo2.0-MediumItalic"
},
artist = new OsuSpriteText
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 45),
TextSize = 15,
Colour = Color4.White,
Text = @"Nothing to play",
Font = @"Exo2.0-BoldItalic"
},
new Container
{
Padding = new MarginPadding { Bottom = progress_height },
Height = bottom_black_area_height,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Children = new Drawable[]
Y = player_height + 10,
},
playerContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = player_height,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffect
{
new FillFlowContainer<Button>
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new[]
{
currentBackground = new Background(),
title = new OsuSpriteText
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new[]
Origin = Anchor.BottomCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 40),
TextSize = 25,
Colour = Color4.White,
Text = @"Nothing to play",
Font = @"Exo2.0-MediumItalic"
},
artist = new OsuSpriteText
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 45),
TextSize = 15,
Colour = Color4.White,
Text = @"Nothing to play",
Font = @"Exo2.0-BoldItalic"
},
new Container
{
Padding = new MarginPadding { Bottom = progress_height },
Height = bottom_black_area_height,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Children = new Drawable[]
{
new Button
new FillFlowContainer<Button>
{
Action = prev,
Icon = FontAwesome.fa_step_backward,
},
playButton = new Button
{
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Action = () =>
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new[]
{
if (current?.Track == null) return;
if (current.Track.IsRunning)
current.Track.Stop();
else
current.Track.Start();
},
Icon = FontAwesome.fa_play_circle_o,
new Button
{
Action = prev,
Icon = FontAwesome.fa_step_backward,
},
playButton = new Button
{
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Action = play,
Icon = FontAwesome.fa_play_circle_o,
},
new Button
{
Action = next,
Icon = FontAwesome.fa_step_forward,
},
}
},
new Button
playlistButton = new Button
{
Action = next,
Icon = FontAwesome.fa_step_forward,
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-bottom_black_area_height / 2, 0),
Icon = FontAwesome.fa_bars,
Action = () => playlist.ToggleVisibility(),
},
}
},
new Button
progressBar = new DragBar
{
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-bottom_black_area_height / 2, 0),
Icon = FontAwesome.fa_bars,
},
}
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Height = progress_height,
Colour = colours.Yellow,
SeekRequested = seek
}
},
},
progress = new DragBar
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Height = progress_height,
Colour = colours.Yellow,
SeekRequested = seek
}
}
}
};
this.beatmaps = beatmaps;
trackManager = game.Audio.Track;
preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode);
preferUnicode.ValueChanged += unicode => updateDisplay(current, TransformDirection.None);
beatmapBacking.BindTo(game.Beatmap);
beatmapSource = game.Beatmap ?? new Bindable<WorkingBeatmap>();
playList = beatmaps.GetAllWithChildren<BeatmapSetInfo>();
currentBackground = new MusicControllerBackground();
dragContainer.Add(currentBackground);
playlist.StateChanged += (c, s) => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, EasingTypes.OutQuint);
}
protected override void LoadComplete()
{
beatmapSource.ValueChanged += workingChanged;
beatmapSource.TriggerChange();
beatmapBacking.ValueChanged += beatmapChanged;
beatmapBacking.TriggerChange();
base.LoadComplete();
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Height = dragContainer.Height;
}
protected override void Update()
{
base.Update();
if (pendingBeatmapSwitch != null)
{
pendingBeatmapSwitch();
pendingBeatmapSwitch = null;
}
if (current?.TrackLoaded ?? false)
{
progress.UpdatePosition(current.Track.Length == 0 ? 0 : (float)(current.Track.CurrentTime / current.Track.Length));
playButton.Icon = current.Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
var track = current.Track;
if (current.Track.HasCompleted && !current.Track.Looping) next();
progressBar.UpdatePosition(track.Length == 0 ? 0 : (float)(track.CurrentTime / track.Length));
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
if (track.HasCompleted && !track.Looping) next();
}
else
playButton.Icon = FontAwesome.fa_play_circle_o;
}
private void workingChanged(WorkingBeatmap beatmap)
private void play()
{
progress.IsEnabled = beatmap != null;
if (beatmap == current) return;
bool audioEquals = current?.BeatmapInfo?.AudioEquals(beatmap?.BeatmapInfo) ?? false;
current = beatmap;
updateDisplay(current, audioEquals ? TransformDirection.None : TransformDirection.Next);
appendToHistory(current?.BeatmapInfo);
}
var track = current?.Track;
private void appendToHistory(BeatmapInfo beatmap)
{
if (beatmap == null) return;
if (playHistoryIndex >= 0)
if (track == null)
{
if (beatmap.AudioEquals(playHistory[playHistoryIndex]))
return;
if (playHistoryIndex < playHistory.Count - 1)
playHistory.RemoveRange(playHistoryIndex + 1, playHistory.Count - playHistoryIndex - 1);
playlist.PlayNext();
return;
}
playHistory.Insert(++playHistoryIndex, beatmap);
if (track.IsRunning)
track.Stop();
else
track.Start();
}
private void prev()
{
if (playHistoryIndex > 0)
play(playHistory[--playHistoryIndex], false);
queuedDirection = TransformDirection.Prev;
playlist.PlayPrevious();
}
private void next()
{
if (playHistoryIndex < playHistory.Count - 1)
play(playHistory[++playHistoryIndex], true);
else
{
if (playList.Count == 0) return;
if (current != null && playList.Count == 1) return;
//shuffle
BeatmapInfo nextToPlay;
do
{
int j = RNG.Next(playListIndex, playList.Count);
if (j != playListIndex)
{
BeatmapSetInfo temp = playList[playListIndex];
playList[playListIndex] = playList[j];
playList[j] = temp;
}
nextToPlay = playList[playListIndex++].Beatmaps[0];
if (playListIndex == playList.Count) playListIndex = 0;
}
while (nextToPlay.AudioEquals(current?.BeatmapInfo));
play(nextToPlay, true);
appendToHistory(nextToPlay);
}
queuedDirection = TransformDirection.Next;
playlist.PlayNext();
}
private void play(BeatmapInfo info, bool isNext)
private WorkingBeatmap current;
private TransformDirection queuedDirection;
private void beatmapChanged(WorkingBeatmap beatmap)
{
current = beatmaps.GetWorkingBeatmap(info, current);
Task.Run(() =>
{
trackManager.SetExclusive(current.Track);
current.Track.Start();
beatmapSource.Value = current;
});
updateDisplay(current, isNext ? TransformDirection.Next : TransformDirection.Prev);
progressBar.IsEnabled = beatmap != null;
bool audioEquals = beatmapBacking.Value?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false;
current = beatmapBacking.Value;
updateDisplay(beatmapBacking, audioEquals ? TransformDirection.None : queuedDirection);
}
private Action pendingBeatmapSwitch;
private ScheduledDelegate pendingBeatmapSwitch;
private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction)
{
//we might be off-screen when this update comes in.
//rather than Scheduling, manually handle this to avoid possible memory contention.
pendingBeatmapSwitch = () =>
pendingBeatmapSwitch?.Cancel();
pendingBeatmapSwitch = Schedule(delegate
{
Task.Run(() =>
{
if (beatmap?.Beatmap == null)
if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
{
title.Current = null;
title.Text = @"Nothing to play";
artist.Current = null;
artist.Text = @"Nothing to play";
}
else
{
BeatmapMetadata metadata = beatmap.Beatmap.BeatmapInfo.Metadata;
title.Text = preferUnicode ? metadata.TitleUnicode : metadata.Title;
artist.Text = preferUnicode ? metadata.ArtistUnicode : metadata.Artist;
title.Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title);
artist.Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist);
}
});
dragContainer.Add(new AsyncLoadWrapper(new MusicControllerBackground(beatmap)
playerContainer.Add(new AsyncLoadWrapper(new Background(beatmap)
{
OnLoadComplete = d =>
{
@@ -344,17 +324,15 @@ namespace osu.Game.Overlays
{
Depth = float.MaxValue,
});
};
});
}
private void seek(float position)
{
current?.Track?.Seek(current.Track.Length * position);
current?.Track?.Start();
var track = current?.Track;
track?.Seek(track.Length * position);
}
private const float transition_length = 800;
protected override void PopIn()
{
base.PopIn();
@@ -371,14 +349,19 @@ namespace osu.Game.Overlays
dragContainer.ScaleTo(0.9f, transition_length, EasingTypes.OutQuint);
}
private enum TransformDirection { None, Next, Prev }
private enum TransformDirection
{
None,
Next,
Prev
}
private class MusicControllerBackground : BufferedContainer
private class Background : BufferedContainer
{
private readonly Sprite sprite;
private readonly WorkingBeatmap beatmap;
public MusicControllerBackground(WorkingBeatmap beatmap = null)
public Background(WorkingBeatmap beatmap = null)
{
this.beatmap = beatmap;
CacheDrawnFrameBuffer = true;
+6 -2
View File
@@ -12,7 +12,11 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Options
{
public class OptionSlider<T> : FillFlowContainer where T : struct
public class OptionSlider<T> : OptionSlider<T, OsuSliderBar<T>> where T: struct
{
}
public class OptionSlider<T, U> : FillFlowContainer where T : struct where U : SliderBar<T>, new()
{
private readonly SliderBar<T> slider;
private readonly SpriteText text;
@@ -50,7 +54,7 @@ namespace osu.Game.Overlays.Options
{
Alpha = 0,
},
slider = new OsuSliderBar<T>
slider = new U()
{
Margin = new MarginPadding { Top = 5, Bottom = 5 },
RelativeSizeAxes = Axes.X
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Audio
{
Children = new Drawable[]
{
new OptionSlider<double>
new OptionSlider<double, OffsetSlider>
{
LabelText = "Audio Offset",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.AudioOffset)
Bindable = config.GetBindable<double>(OsuConfig.AudioOffset)
},
new OsuButton
{
@@ -30,5 +29,10 @@ namespace osu.Game.Overlays.Options.Sections.Audio
}
};
}
private class OffsetSlider : OsuSliderBar<double>
{
public override string TooltipText => Current.Value.ToString(@"0ms");
}
}
}
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay
{
Children = new Drawable[]
{
new OptionSlider<int>
new OptionSlider<double>
{
LabelText = "Background dim",
Bindable = (BindableInt)config.GetBindable<int>(OsuConfig.DimLevel)
Bindable = config.GetBindable<double>(OsuConfig.DimLevel)
},
new OptionEnumDropdown<ProgressBarType>
{
@@ -36,7 +35,7 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay
new OptionSlider<double>
{
LabelText = "Score meter size",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.ScoreMeterScale)
Bindable = config.GetBindable<double>(OsuConfig.ScoreMeterScale)
},
new OsuCheckbox
{
@@ -2,9 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Options.Sections.Gameplay
{
@@ -17,18 +17,23 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay
{
Children = new Drawable[]
{
new OptionSlider<double>
new OptionSlider<double, StarSlider>
{
LabelText = "Display beatmaps from",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.DisplayStarsMinimum)
Bindable = config.GetBindable<double>(OsuConfig.DisplayStarsMinimum)
},
new OptionSlider<double>
new OptionSlider<double, StarSlider>
{
LabelText = "up to",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.DisplayStarsMaximum)
Bindable = config.GetBindable<double>(OsuConfig.DisplayStarsMaximum)
},
};
}
private class StarSlider : OsuSliderBar<double>
{
public override string TooltipText => Current.Value.ToString(@"0.## stars");
}
}
}
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Options.Sections.General
protected override string Header => "Language";
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load(OsuConfigManager osuConfig, FrameworkConfigManager frameworkConfig)
{
Children = new Drawable[]
{
@@ -21,12 +22,12 @@ namespace osu.Game.Overlays.Options.Sections.General
new OsuCheckbox
{
LabelText = "Prefer metadata in original language",
Bindable = config.GetBindable<bool>(OsuConfig.ShowUnicode)
Bindable = frameworkConfig.GetBindable<bool>(FrameworkConfig.ShowUnicode)
},
new OsuCheckbox
{
LabelText = "Use alternative font for chat display",
Bindable = config.GetBindable<bool>(OsuConfig.AlternativeChatFont)
Bindable = osuConfig.GetBindable<bool>(OsuConfig.AlternativeChatFont)
},
};
}
@@ -12,8 +12,8 @@ namespace osu.Game.Overlays.Options.Sections.Graphics
{
protected override string Header => "Layout";
private OptionSlider<int> letterboxPositionX;
private OptionSlider<int> letterboxPositionY;
private OptionSlider<double> letterboxPositionX;
private OptionSlider<double> letterboxPositionY;
private Bindable<bool> letterboxing;
@@ -35,15 +35,15 @@ namespace osu.Game.Overlays.Options.Sections.Graphics
LabelText = "Letterboxing",
Bindable = letterboxing,
},
letterboxPositionX = new OptionSlider<int>
letterboxPositionX = new OptionSlider<double>
{
LabelText = "Horizontal position",
Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionX)
Bindable = config.GetBindable<double>(FrameworkConfig.LetterboxPositionX)
},
letterboxPositionY = new OptionSlider<int>
letterboxPositionY = new OptionSlider<double>
{
LabelText = "Vertical position",
Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionY)
Bindable = config.GetBindable<double>(FrameworkConfig.LetterboxPositionY)
},
};
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Input
{
Children = new Drawable[]
{
new OptionSlider<double>
new OptionSlider<double, SensitivitySlider>
{
LabelText = "Sensitivity",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.MouseSpeed),
Bindable = config.GetBindable<double>(OsuConfig.MouseSpeed)
},
new OsuCheckbox
{
@@ -55,5 +54,10 @@ namespace osu.Game.Overlays.Options.Sections.Input
},
};
}
private class SensitivitySlider : OsuSliderBar<double>
{
public override string TooltipText => Current.Value.ToString(@"0.##x");
}
}
}
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics;
@@ -59,15 +58,15 @@ namespace osu.Game.Overlays.Options.Sections
LabelText = "Always use skin cursor",
Bindable = config.GetBindable<bool>(OsuConfig.UseSkinCursor)
},
new OptionSlider<double>
new OptionSlider<double, SizeSlider>
{
LabelText = "Menu cursor size",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.MenuCursorSize)
Bindable = config.GetBindable<double>(OsuConfig.MenuCursorSize)
},
new OptionSlider<double>
new OptionSlider<double, SizeSlider>
{
LabelText = "Gameplay cursor size",
Bindable = (BindableDouble)config.GetBindable<double>(OsuConfig.GameplayCursorSize)
Bindable = config.GetBindable<double>(OsuConfig.GameplayCursorSize)
},
new OsuCheckbox
{
@@ -76,5 +75,10 @@ namespace osu.Game.Overlays.Options.Sections
},
};
}
private class SizeSlider : OsuSliderBar<double>
{
public override string TooltipText => Current.Value.ToString(@"0.##x");
}
}
}
@@ -1,49 +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 OpenTK;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Objects
{
public class CurvedHitObject : HitObject, IHasCurve
{
public SliderCurve Curve { get; } = new SliderCurve();
public int RepeatCount { get; set; } = 1;
public double EndTime => 0;
public double Duration => 0;
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public CurveType CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
var p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
}
}
+23 -12
View File
@@ -4,7 +4,7 @@
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects
{
@@ -19,12 +19,16 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// The time at which the HitObject starts.
/// </summary>
public double StartTime { get; set; }
public double StartTime;
/// <summary>
/// The samples to be played when this hit object is hit.
/// <para>
/// In the case of <see cref="IHasRepeats"/> types, this is the sample of the curve body
/// and can be treated as the default samples for the hit object.
/// </para>
/// </summary>
public List<SampleInfo> Samples = new List<SampleInfo>();
public SampleInfoList Samples = new SampleInfoList();
/// <summary>
/// Applies default values to this HitObject.
@@ -36,17 +40,24 @@ namespace osu.Game.Rulesets.Objects
ControlPoint overridePoint;
ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint);
foreach (var sample in Samples)
{
if (sample.Volume == 0)
sample.Volume = (overridePoint ?? timingPoint)?.SampleVolume ?? 0;
ControlPoint samplePoint = overridePoint ?? timingPoint;
// If the bank is not assigned a name, assign it from the control point
if (!string.IsNullOrEmpty(sample.Bank))
continue;
// Initialize first sample
Samples.ForEach(s => initializeSampleInfo(s, samplePoint));
sample.Bank = (overridePoint ?? timingPoint)?.SampleBank ?? @"normal";
}
// Initialize any repeat samples
var repeatData = this as IHasRepeats;
repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint)));
}
private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint)
{
if (sample.Volume == 0)
sample.Volume = controlPoint?.SampleVolume ?? 0;
// If the bank is not assigned a name, assign it from the control point
if (string.IsNullOrEmpty(sample.Bank))
sample.Bank = controlPoint?.SampleBank ?? @"normal";
}
}
}
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
/// <summary>
/// Legacy osu!catch Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Hit : HitObject, IHasCombo, IHasXPosition
internal sealed class ConvertHit : HitObject, IHasCombo, IHasXPosition
{
public float X { get; set; }
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -10,33 +11,34 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
/// <summary>
/// A HitObjectParser to parse legacy osu!catch Beatmaps.
/// </summary>
internal class HitObjectParser : Legacy.HitObjectParser
internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
protected override HitObject CreateHit(Vector2 position, bool newCombo)
{
return new Hit
return new ConvertHit
{
X = position.X,
NewCombo = newCombo,
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<SampleInfoList> repeatSamples)
{
return new Slider
return new ConvertSlider
{
X = position.X,
NewCombo = newCombo,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, double endTime)
{
return new Spinner
return new ConvertSpinner
{
EndTime = endTime
};
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
/// <summary>
/// Legacy osu!catch Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Slider : CurvedHitObject, IHasXPosition, IHasCombo
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo
{
public float X { get; set; }
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
/// <summary>
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Spinner : HitObject, IHasEndTime
internal sealed class ConvertSpinner : HitObject, IHasEndTime
{
public double EndTime { get; set; }
@@ -14,29 +14,28 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <summary>
/// A HitObjectParser to parse legacy Beatmaps.
/// </summary>
internal abstract class HitObjectParser : Objects.HitObjectParser
internal abstract class ConvertHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text)
{
string[] split = text.Split(',');
var type = (HitObjectType)int.Parse(split[3]) & ~HitObjectType.ColourHax;
bool combo = type.HasFlag(HitObjectType.NewCombo);
type &= ~HitObjectType.NewCombo;
ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax;
bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
type &= ~ConvertHitObjectType.NewCombo;
int sampleVolume = 0;
string normalSampleBank = null;
string addSampleBank = null;
var soundType = (LegacySoundType)int.Parse(split[4]);
var bankInfo = new SampleBankInfo();
HitObject result;
if ((type & HitObjectType.Circle) > 0)
if ((type & ConvertHitObjectType.Circle) > 0)
{
result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo);
if (split.Length > 5)
readCustomSampleBanks(split[5], ref normalSampleBank, ref addSampleBank, ref sampleVolume);
readCustomSampleBanks(split[5], bankInfo);
}
else if ((type & HitObjectType.Slider) > 0)
else if ((type & ConvertHitObjectType.Slider) > 0)
{
CurveType curveType = CurveType.Catmull;
double length = 0;
@@ -77,26 +76,74 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount);
if (split.Length > 10)
readCustomSampleBanks(split[10], ref normalSampleBank, ref addSampleBank, ref sampleVolume);
readCustomSampleBanks(split[10], bankInfo);
// One node for each repeat + the start and end nodes
// Note that the first length of the slider is considered a repeat, but there are no actual repeats happening
int nodes = Math.Max(0, repeatCount - 1) + 2;
// Populate node sample bank infos with the default hit object sample bank
var nodeBankInfos = new List<SampleBankInfo>();
for (int i = 0; i < nodes; i++)
nodeBankInfos.Add(bankInfo.Clone());
// Read any per-node sample banks
if (split.Length > 9 && split[9].Length > 0)
{
string[] sets = split[9].Split('|');
for (int i = 0; i < nodes; i++)
{
if (i >= sets.Length)
break;
SampleBankInfo info = nodeBankInfos[i];
readCustomSampleBanks(sets[i], info);
}
}
// Populate node sound types with the default hit object sound type
var nodeSoundTypes = new List<LegacySoundType>();
for (int i = 0; i < nodes; i++)
nodeSoundTypes.Add(soundType);
// Read any per-node sound types
if (split.Length > 8 && split[8].Length > 0)
{
string[] adds = split[8].Split('|');
for (int i = 0; i < nodes; i++)
{
if (i >= adds.Length)
break;
int sound;
int.TryParse(adds[i], out sound);
nodeSoundTypes[i] = (LegacySoundType)sound;
}
}
// Generate the final per-node samples
var nodeSamples = new List<SampleInfoList>(nodes);
for (int i = 0; i <= repeatCount; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples);
}
else if ((type & HitObjectType.Spinner) > 0)
else if ((type & ConvertHitObjectType.Spinner) > 0)
{
result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture));
if (split.Length > 6)
readCustomSampleBanks(split[6], ref normalSampleBank, ref addSampleBank, ref sampleVolume);
readCustomSampleBanks(split[6], bankInfo);
}
else if ((type & HitObjectType.Hold) > 0)
else if ((type & ConvertHitObjectType.Hold) > 0)
{
// Note: Hold is generated by BMS converts
// Todo: Apparently end time is determined by samples??
// Shouldn't need implementation until mania
result = new Hold
result = new ConvertHold
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
@@ -106,50 +153,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
throw new InvalidOperationException($@"Unknown hit object type {type}");
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
var soundType = (LegacySoundType)int.Parse(split[4]);
result.Samples.Add(new SampleInfo
{
Bank = normalSampleBank,
Name = SampleInfo.HIT_NORMAL,
Volume = sampleVolume
});
if ((soundType & LegacySoundType.Finish) > 0)
{
result.Samples.Add(new SampleInfo
{
Bank = addSampleBank,
Name = SampleInfo.HIT_FINISH,
Volume = sampleVolume
});
}
if ((soundType & LegacySoundType.Whistle) > 0)
{
result.Samples.Add(new SampleInfo
{
Bank = addSampleBank,
Name = SampleInfo.HIT_WHISTLE,
Volume = sampleVolume
});
}
if ((soundType & LegacySoundType.Clap) > 0)
{
result.Samples.Add(new SampleInfo
{
Bank = addSampleBank,
Name = SampleInfo.HIT_CLAP,
Volume = sampleVolume
});
}
result.Samples = convertSoundType(soundType, bankInfo);
return result;
}
private void readCustomSampleBanks(string str, ref string normalSampleBank, ref string addSampleBank, ref int sampleVolume)
private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
{
if (string.IsNullOrEmpty(str))
return;
@@ -169,9 +178,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (stringAddBank == @"none")
stringAddBank = null;
normalSampleBank = stringBank;
addSampleBank = stringAddBank;
sampleVolume = split.Length > 3 ? int.Parse(split[3]) : 0;
bankInfo.Normal = stringBank;
bankInfo.Add = stringAddBank;
if (split.Length > 3)
bankInfo.Volume = int.Parse(split[3]);
}
/// <summary>
@@ -191,8 +202,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="length">The slider length.</param>
/// <param name="curveType">The slider curve type.</param>
/// <param name="repeatCount">The slider repeat count.</param>
/// <param name="repeatSamples">The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount);
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<SampleInfoList> repeatSamples);
/// <summary>
/// Creates a legacy Spinner-type hit object.
@@ -202,6 +214,63 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSpinner(Vector2 position, double endTime);
private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{
var soundTypes = new SampleInfoList
{
new SampleInfo
{
Bank = bankInfo.Normal,
Name = SampleInfo.HIT_NORMAL,
Volume = bankInfo.Volume
}
};
if ((type & LegacySoundType.Finish) > 0)
{
soundTypes.Add(new SampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_FINISH,
Volume = bankInfo.Volume
});
}
if ((type & LegacySoundType.Whistle) > 0)
{
soundTypes.Add(new SampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_WHISTLE,
Volume = bankInfo.Volume
});
}
if ((type & LegacySoundType.Clap) > 0)
{
soundTypes.Add(new SampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_CLAP,
Volume = bankInfo.Volume
});
}
return soundTypes;
}
private class SampleBankInfo
{
public string Normal;
public string Add;
public int Volume;
public SampleBankInfo Clone()
{
return (SampleBankInfo)MemberwiseClone();
}
}
[Flags]
private enum LegacySoundType
{
@@ -6,7 +6,7 @@ using System;
namespace osu.Game.Rulesets.Objects.Legacy
{
[Flags]
public enum HitObjectType
internal enum ConvertHitObjectType
{
Circle = 1 << 0,
Slider = 1 << 1,
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <summary>
/// Legacy Hold-type, used for parsing "specials" in beatmaps.
/// </summary>
internal sealed class Hold : HitObject, IHasPosition, IHasCombo, IHasHold
internal sealed class ConvertHold : HitObject, IHasPosition, IHasCombo, IHasHold
{
public Vector2 Position { get; set; }
@@ -0,0 +1,39 @@
// 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.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using OpenTK;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Objects.Legacy
{
internal abstract class ConvertSlider : HitObject, IHasCurve
{
public List<Vector2> ControlPoints { get; set; }
public CurveType CurveType { get; set; }
public double Distance { get; set; }
public List<SampleInfoList> RepeatSamples { get; set; }
public int RepeatCount { get; set; } = 1;
public double EndTime { get; set; }
public double Duration { get; set; }
public Vector2 PositionAt(double progress)
{
throw new NotImplementedException();
}
public double ProgressAt(double progress)
{
throw new NotImplementedException();
}
public int RepeatAt(double progress)
{
throw new NotImplementedException();
}
}
}
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
/// <summary>
/// Legacy osu!mania Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Hit : HitObject, IHasXPosition, IHasCombo
internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo
{
public float X { get; set; }
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -10,33 +11,34 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
/// <summary>
/// A HitObjectParser to parse legacy osu!mania Beatmaps.
/// </summary>
internal class HitObjectParser : Legacy.HitObjectParser
internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
protected override HitObject CreateHit(Vector2 position, bool newCombo)
{
return new Hit
return new ConvertHit
{
X = position.X,
NewCombo = newCombo,
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<SampleInfoList> repeatSamples)
{
return new Slider
return new ConvertSlider
{
X = position.X,
NewCombo = newCombo,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, double endTime)
{
return new Spinner
return new ConvertSpinner
{
X = position.X,
EndTime = endTime
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
/// <summary>
/// Legacy osu!mania Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Slider : CurvedHitObject, IHasXPosition, IHasCombo
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo
{
public float X { get; set; }
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
/// <summary>
/// Legacy osu!mania Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Spinner : HitObject, IHasEndTime, IHasXPosition
internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition
{
public double EndTime { get; set; }
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
/// <summary>
/// Legacy osu! Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Hit : HitObject, IHasPosition, IHasCombo
internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo
{
public Vector2 Position { get; set; }
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -10,33 +11,34 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
/// <summary>
/// A HitObjectParser to parse legacy osu! Beatmaps.
/// </summary>
internal class HitObjectParser : Legacy.HitObjectParser
internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
protected override HitObject CreateHit(Vector2 position, bool newCombo)
{
return new Hit
return new ConvertHit
{
Position = position,
NewCombo = newCombo,
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<SampleInfoList> repeatSamples)
{
return new Slider
return new ConvertSlider
{
Position = position,
NewCombo = newCombo,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, double endTime)
{
return new Spinner
return new ConvertSpinner
{
Position = position,
EndTime = endTime
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
/// <summary>
/// Legacy osu! Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Slider : CurvedHitObject, IHasPosition, IHasCombo
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo
{
public Vector2 Position { get; set; }
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
/// <summary>
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Spinner : HitObject, IHasEndTime, IHasPosition
internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition
{
public double EndTime { get; set; }
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// <summary>
/// Legacy osu!taiko Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Hit : HitObject, IHasCombo
internal sealed class ConvertHit : HitObject, IHasCombo
{
public bool NewCombo { get; set; }
}
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -10,31 +11,32 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// <summary>
/// A HitObjectParser to parse legacy osu!taiko Beatmaps.
/// </summary>
internal class HitObjectParser : Legacy.HitObjectParser
internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
protected override HitObject CreateHit(Vector2 position, bool newCombo)
{
return new Hit
return new ConvertHit
{
NewCombo = newCombo,
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<SampleInfoList> repeatSamples)
{
return new Slider
return new ConvertSlider
{
NewCombo = newCombo,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, double endTime)
{
return new Spinner
return new ConvertSpinner
{
EndTime = endTime
};
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// <summary>
/// Legacy osu!taiko Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Slider : CurvedHitObject, IHasCombo
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo
{
public bool NewCombo { get; set; }
}
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// <summary>
/// Legacy osu!taiko Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class Spinner : HitObject, IHasEndTime
internal sealed class ConvertSpinner : HitObject, IHasEndTime
{
public double EndTime { get; set; }
@@ -11,11 +11,6 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary>
public interface IHasCurve : IHasDistance, IHasRepeats
{
/// <summary>
/// The curve.
/// </summary>
SliderCurve Curve { get; }
/// <summary>
/// The control points that shape the curve.
/// </summary>
@@ -1,6 +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
using osu.Game.Audio;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
@@ -12,5 +15,10 @@ namespace osu.Game.Rulesets.Objects.Types
/// The amount of times the HitObject repeats.
/// </summary>
int RepeatCount { get; }
/// <summary>
/// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc).
/// </summary>
List<SampleInfoList> RepeatSamples { get; }
}
}
@@ -0,0 +1,39 @@
// 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.Rulesets.Objects;
using osu.Game.Beatmaps;
namespace osu.Game.Rulesets.Replays
{
public abstract class AutoGenerator<T> : IAutoGenerator
where T : HitObject
{
/// <summary>
/// Creates the auto replay and returns it.
/// Every subclass of OsuAutoGeneratorBase should implement this!
/// </summary>
public abstract Replay Generate();
#region Parameters
/// <summary>
/// The beatmap we're making.
/// </summary>
protected Beatmap<T> Beatmap;
#endregion
protected AutoGenerator(Beatmap<T> beatmap)
{
Beatmap = beatmap;
}
#region Constants
// Shared amongst all modes
protected const double KEY_UP_DELAY = 50;
#endregion
}
}
@@ -0,0 +1,10 @@
// 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.Rulesets.Replays
{
public interface IAutoGenerator
{
Replay Generate();
}
}
-2
View File
@@ -8,8 +8,6 @@ namespace osu.Game.Rulesets.Replays
{
public class Replay
{
protected const double KEY_UP_DELAY = 50;
public User User;
public List<ReplayFrame> Frames = new List<ReplayFrame>();
+1 -1
View File
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.UI
where TObject : HitObject
{
/// <summary>
/// The Beatmap
/// The Beatmap
/// </summary>
public Beatmap<TObject> Beatmap;
+1
View File
@@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.UI
{
content = new Container
{
AlwaysReceiveInput = true,
RelativeSizeAxes = Axes.Both,
}
}
+22 -10
View File
@@ -5,8 +5,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using OpenTK.Graphics;
@@ -56,25 +58,33 @@ namespace osu.Game.Screens.Menu
};
}
private Bindable<bool> menuVoice;
private Bindable<bool> menuMusic;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
private void load(AudioManager audio, OsuConfigManager config)
{
welcome = audio.Sample.Get(@"welcome");
seeya = audio.Sample.Get(@"seeya");
menuVoice = config.GetBindable<bool>(OsuConfig.MenuVoice);
menuMusic = config.GetBindable<bool>(OsuConfig.MenuMusic);
bgm = audio.Track.Get(@"circles");
bgm.Looping = true;
welcome = audio.Sample.Get(@"welcome");
seeya = audio.Sample.Get(@"seeya");
}
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
welcome.Play();
if (menuVoice)
welcome.Play();
Scheduler.AddDelayed(delegate
{
bgm.Start();
if (menuMusic)
bgm.Start();
LoadComponentAsync(mainMenu = new MainMenu());
@@ -109,15 +119,17 @@ namespace osu.Game.Screens.Menu
if (!(last is MainMenu))
Content.FadeIn(300);
double fadeOutTime = 2000;
//we also handle the exit transition.
seeya.Play();
if (menuVoice)
seeya.Play();
else
fadeOutTime = 500;
const double fade_out_time = 2000;
Scheduler.AddDelayed(Exit, fade_out_time);
Scheduler.AddDelayed(Exit, fadeOutTime);
//don't want to fade out completely else we will stop running updates and shit will hit the fan.
Game.FadeTo(0.01f, fade_out_time);
Game.FadeTo(0.01f, fadeOutTime);
base.OnResuming(last);
}
+35 -1
View File
@@ -2,8 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
@@ -15,6 +21,7 @@ using osu.Game.Screens.Select;
using osu.Game.Screens.Tournament;
using osu.Framework.Input;
using OpenTK.Input;
using System.Threading.Tasks;
namespace osu.Game.Screens.Menu
{
@@ -54,11 +61,27 @@ namespace osu.Game.Screens.Menu
};
}
private Bindable<bool> menuMusic;
private TrackManager trackManager;
private WorkingBeatmap song;
[BackgroundDependencyLoader]
private void load(OsuGame game)
private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps)
{
menuMusic = config.GetBindable<bool>(OsuConfig.MenuMusic);
LoadComponentAsync(background);
if (!menuMusic)
{
trackManager = game.Audio.Track;
int choosableBeatmapsetAmmount = beatmaps.Query<BeatmapSetInfo>().Count();
if (choosableBeatmapsetAmmount > 0)
{
song = beatmaps.GetWorkingBeatmap(beatmaps.GetWithChildren<BeatmapSetInfo>(RNG.Next(1, choosableBeatmapsetAmmount)).Beatmaps[0]);
Beatmap = song;
}
}
buttons.OnSettings = game.ToggleOptions;
preloadSongSelect();
@@ -81,6 +104,17 @@ namespace osu.Game.Screens.Menu
{
base.OnEntering(last);
buttons.FadeInFromZero(500);
if (last is Intro && song != null)
{
Task.Run(() =>
{
trackManager.SetExclusive(song.Track);
song.Track.Seek(song.Beatmap.Metadata.PreviewTime);
if (song.Beatmap.Metadata.PreviewTime == -1)
song.Track.Seek(song.Track.Length * 0.4f);
song.Track.Start();
});
}
}
protected override void OnSuspending(Screen next)
+48 -33
View File
@@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play
public Action RestartRequested;
public bool IsPaused => !interpolatedSourceClock.IsRunning;
public bool IsPaused => !decoupledClock.IsRunning;
internal override bool AllowRulesetChange => false;
@@ -51,9 +51,9 @@ namespace osu.Game.Screens.Play
private bool canPause => ValidForResume && !HasFailed && Time.Current >= lastPauseActionTime + pause_cooldown;
private IAdjustableClock sourceClock;
private OffsetClock offsetClock;
private IFrameBasedClock interpolatedSourceClock;
private IAdjustableClock adjustableSourceClock;
private FramedOffsetClock offsetClock;
private DecoupleableInterpolatingFramedClock decoupledClock;
private RulesetInfo ruleset;
@@ -62,7 +62,7 @@ namespace osu.Game.Screens.Play
#region User Settings
private Bindable<int> dimLevel;
private Bindable<double> dimLevel;
private Bindable<bool> mouseWheelDisabled;
private Bindable<double> userAudioOffset;
@@ -70,6 +70,8 @@ namespace osu.Game.Screens.Play
private SkipButton skipButton;
private Container hitRendererContainer;
private HudOverlay hudOverlay;
private PauseOverlay pauseOverlay;
private FailOverlay failOverlay;
@@ -77,7 +79,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(permitNulls: true)]
private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuConfigManager config, OsuGame osu)
{
dimLevel = config.GetBindable<int>(OsuConfig.DimLevel);
dimLevel = config.GetBindable<double>(OsuConfig.DimLevel);
mouseWheelDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableWheel);
Ruleset rulesetInstance;
@@ -87,10 +89,7 @@ namespace osu.Game.Screens.Play
if (Beatmap == null)
Beatmap = beatmaps.GetWorkingBeatmap(BeatmapInfo, withStoryboard: true);
if ((Beatmap?.Beatmap?.HitObjects.Count ?? 0) == 0)
throw new Exception("No valid objects were found!");
if (Beatmap == null)
if (Beatmap?.Beatmap == null)
throw new Exception("Beatmap was not loaded");
ruleset = osu?.Ruleset.Value ?? Beatmap.BeatmapInfo.Ruleset;
@@ -108,6 +107,9 @@ namespace osu.Game.Screens.Play
rulesetInstance = ruleset.CreateInstance();
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap);
}
if (!HitRenderer.Objects.Any())
throw new Exception("Beatmap contains no hit objects!");
}
catch (Exception e)
{
@@ -123,23 +125,31 @@ namespace osu.Game.Screens.Play
if (track != null)
{
audio.Track.SetExclusive(track);
sourceClock = track;
adjustableSourceClock = track;
}
sourceClock = (IAdjustableClock)track ?? new StopwatchClock();
offsetClock = new OffsetClock(sourceClock);
adjustableSourceClock = (IAdjustableClock)track ?? new StopwatchClock();
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = HitRenderer.Objects.First().StartTime;
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);
userAudioOffset = config.GetBindable<double>(OsuConfig.AudioOffset);
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
userAudioOffset.TriggerChange();
interpolatedSourceClock = new InterpolatingFramedClock(offsetClock);
Schedule(() =>
{
sourceClock.Reset();
adjustableSourceClock.Reset();
foreach (var mod in Beatmap.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(sourceClock);
mod.ApplyToClock(adjustableSourceClock);
decoupledClock.ChangeSource(adjustableSourceClock);
});
scoreProcessor = HitRenderer.CreateScoreProcessor();
@@ -155,7 +165,9 @@ namespace osu.Game.Screens.Play
hudOverlay.BindHitRenderer(HitRenderer);
hudOverlay.Progress.Objects = HitRenderer.Objects;
hudOverlay.Progress.AudioClock = interpolatedSourceClock;
hudOverlay.Progress.AudioClock = decoupledClock;
hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded;
hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos);
//bind HitRenderer to ScoreProcessor and ourselves (for a pass situation)
HitRenderer.OnAllJudged += onCompletion;
@@ -165,16 +177,20 @@ namespace osu.Game.Screens.Play
Children = new Drawable[]
{
new Container
hitRendererContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = interpolatedSourceClock,
Children = new Drawable[]
{
HitRenderer,
skipButton = new SkipButton
new Container
{
Alpha = 0
RelativeSizeAxes = Axes.Both,
Clock = offsetClock,
Children = new Drawable[]
{
HitRenderer,
skipButton = new SkipButton { Alpha = 0 },
}
},
}
},
@@ -224,7 +240,7 @@ namespace osu.Game.Screens.Play
skipButton.Action = () =>
{
sourceClock.Seek(firstHitObject - skip_required_cutoff - fade_time);
decoupledClock.Seek(firstHitObject - skip_required_cutoff - fade_time);
skipButton.Action = null;
};
@@ -241,7 +257,7 @@ namespace osu.Game.Screens.Play
// we want to wait for the source clock to stop so we can be sure all components are in a stable state.
if (!IsPaused)
{
sourceClock.Stop();
decoupledClock.Stop();
Schedule(() => Pause(force));
return;
@@ -268,7 +284,7 @@ namespace osu.Game.Screens.Play
hudOverlay.KeyCounter.IsCounting = true;
hudOverlay.Progress.Hide();
pauseOverlay.Hide();
sourceClock.Start();
decoupledClock.Start();
}
public void Restart()
@@ -304,7 +320,7 @@ namespace osu.Game.Screens.Play
private void onFail()
{
sourceClock.Stop();
decoupledClock.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
@@ -316,11 +332,11 @@ namespace osu.Game.Screens.Play
base.OnEntering(last);
(Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, EasingTypes.OutQuint);
Background?.FadeTo((100f - dimLevel) / 100, 1500, EasingTypes.OutQuint);
Background?.FadeTo(1 - (float)dimLevel, 1500, EasingTypes.OutQuint);
Content.Alpha = 0;
dimLevel.ValueChanged += newDim => Background?.FadeTo((100f - newDim) / 100, 800);
dimLevel.ValueChanged += newDim => Background?.FadeTo(1 - (float)newDim, 800);
Content.ScaleTo(0.7f);
@@ -332,13 +348,12 @@ namespace osu.Game.Screens.Play
Delay(750);
Schedule(() =>
{
sourceClock.Start();
decoupledClock.Start();
initializeSkipButton();
});
//keep in mind this is using the interpolatedSourceClock so won't be run as early as we may expect.
HitRenderer.Alpha = 0;
HitRenderer.FadeIn(750, EasingTypes.OutQuint);
hitRendererContainer.Alpha = 0;
hitRendererContainer.FadeIn(750, EasingTypes.OutQuint);
}
protected override void OnSuspending(Screen next)
+85 -17
View File
@@ -1,21 +1,28 @@
// 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.Input;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
namespace osu.Game.Screens.Play
{
public class PlayerInputManager : PassThroughInputManager
{
private readonly ManualClock clock = new ManualClock();
private ManualClock clock;
private IFrameBasedClock parentClock;
private ReplayInputHandler replayInputHandler;
public ReplayInputHandler ReplayInputHandler
{
get { return replayInputHandler; }
get
{
return replayInputHandler;
}
set
{
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
@@ -28,44 +35,105 @@ namespace osu.Game.Screens.Play
}
}
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableButtons);
}
protected override void LoadComplete()
{
base.LoadComplete();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
Clock = new FramedClock(clock);
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
}
/// <summary>
/// Whether we running up-to-date with our parent clock.
/// If not, we will need to keep processing children until we catch up.
/// </summary>
private bool requireMoreUpdateLoops;
/// <summary>
/// Whether we in a valid state (ie. should we keep processing children frames).
/// This should be set to false when the replay is, for instance, waiting for future frames to arrive.
/// </summary>
private bool validState;
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
private bool isAttached => replayInputHandler != null && !UseParentState;
private const int max_catch_up_updates_per_frame = 50;
public override bool UpdateSubTree()
{
requireMoreUpdateLoops = true;
validState = true;
int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
if (!base.UpdateSubTree())
return false;
return true;
}
protected override void Update()
{
base.Update();
if (parentClock == null) return;
clock.Rate = parentClock.Rate;
clock.IsRunning = parentClock.IsRunning;
//if a replayHandler is not attached, we should just pass-through.
if (UseParentState || replayInputHandler == null)
if (!isAttached)
{
clock.CurrentTime = parentClock.CurrentTime;
base.Update();
return;
}
while (true)
else
{
double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime);
if (newTime == null)
//we shouldn't execute for this time value
break;
if (clock.CurrentTime == parentClock.CurrentTime)
break;
{
// we shouldn't execute for this time value. probably waiting on more replay data.
validState = false;
return;
}
clock.CurrentTime = newTime.Value;
base.Update();
}
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime;
base.Update();
}
protected override void TransformState(InputState state)
{
base.TransformState(state);
// we don't want to transform the state if a replay is present (for now, at least).
if (replayInputHandler != null) return;
var mouse = state.Mouse as Framework.Input.MouseState;
if (mouse != null)
{
if (mouseDisabled.Value)
{
mouse.SetPressed(MouseButton.Left, false);
mouse.SetPressed(MouseButton.Right, false);
}
}
}
}
+11 -3
View File
@@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Menu;
using OpenTK;
using osu.Framework.Localisation;
namespace osu.Game.Screens.Play
{
@@ -174,10 +175,17 @@ namespace osu.Game.Screens.Play
}
};
}
}
private readonly WorkingBeatmap beatmap;
public BeatmapMetadataDisplay(WorkingBeatmap beatmap)
{
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation)
{
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
@@ -194,7 +202,7 @@ namespace osu.Game.Screens.Play
{
new OsuSpriteText
{
Text = metadata.Title,
Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title),
TextSize = 36,
Font = @"Exo2.0-MediumItalic",
Origin = Anchor.TopCentre,
@@ -202,7 +210,7 @@ namespace osu.Game.Screens.Play
},
new OsuSpriteText
{
Text = metadata.Artist,
Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist),
TextSize = 26,
Font = @"Exo2.0-MediumItalic",
Origin = Anchor.TopCentre,
+20 -8
View File
@@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play
private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1;
private double firstHitTime => objects.First().StartTime;
private IEnumerable<HitObject> objects;
public IEnumerable<HitObject> Objects
@@ -75,7 +77,7 @@ namespace osu.Game.Screens.Play
Origin = Anchor.BottomLeft,
SeekRequested = delegate (float position)
{
OnSeek?.Invoke(position);
OnSeek?.Invoke(firstHitTime + position * (lastHitTime - firstHitTime));
},
},
};
@@ -86,18 +88,28 @@ namespace osu.Game.Screens.Play
State = Visibility.Visible;
}
private bool barVisible;
private bool allowSeeking;
public void ToggleBar()
public bool AllowSeeking
{
barVisible = !barVisible;
updateBarVisibility();
get
{
return allowSeeking;
}
set
{
if (allowSeeking == value) return;
allowSeeking = value;
updateBarVisibility();
}
}
private void updateBarVisibility()
{
bar.FadeTo(barVisible ? 1 : 0, transition_duration, EasingTypes.In);
MoveTo(new Vector2(0, barVisible ? 0 : bottom_bar_height), transition_duration, EasingTypes.In);
bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, EasingTypes.In);
MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, EasingTypes.In);
}
protected override void PopIn()
@@ -118,7 +130,7 @@ namespace osu.Game.Screens.Play
if (objects == null)
return;
double progress = (AudioClock?.CurrentTime ?? Time.Current) / lastHitTime;
double progress = ((AudioClock?.CurrentTime ?? Time.Current) - firstHitTime) / lastHitTime;
bar.UpdatePosition((float)progress);
graph.Progress = (int)(graph.ColumnCount * progress);
+16 -14
View File
@@ -70,24 +70,26 @@ namespace osu.Game.Screens.Ranking
circleOuterBackground.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleOuterBackground.FadeTo(1, transition_time, EasingTypes.OutQuint);
Content.Delay(transition_time * 0.25f, true);
using (BeginDelayedSequence(transition_time * 0.25f, true))
{
circleOuter.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleOuter.FadeTo(1, transition_time, EasingTypes.OutQuint);
circleOuter.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleOuter.FadeTo(1, transition_time, EasingTypes.OutQuint);
Content.Delay(transition_time * 0.3f, true);
using (BeginDelayedSequence(transition_time * 0.3f, true))
{
backgroundParallax.FadeIn(transition_time, EasingTypes.OutQuint);
backgroundParallax.FadeIn(transition_time, EasingTypes.OutQuint);
circleInner.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleInner.FadeTo(1, transition_time, EasingTypes.OutQuint);
circleInner.ScaleTo(1, transition_time, EasingTypes.OutQuint);
circleInner.FadeTo(1, transition_time, EasingTypes.OutQuint);
Content.Delay(transition_time * 0.4f, true);
modeChangeButtons.FadeIn(transition_time, EasingTypes.OutQuint);
currentPage.FadeIn(transition_time, EasingTypes.OutQuint);
Content.DelayReset();
using (BeginDelayedSequence(transition_time * 0.4f, true))
{
modeChangeButtons.FadeIn(transition_time, EasingTypes.OutQuint);
currentPage.FadeIn(transition_time, EasingTypes.OutQuint);
}
}
}
}
protected override bool OnExiting(Screen next)
+14 -23
View File
@@ -1,30 +1,29 @@
// 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.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
using System;
using System.Collections.Generic;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps;
using osu.Game.Screens.Play;
using osu.Game.Rulesets.Scoring;
using osu.Framework.Graphics.Colour;
using System.Linq;
namespace osu.Game.Screens.Ranking
{
@@ -272,8 +271,6 @@ namespace osu.Game.Screens.Ranking
{
private readonly BeatmapInfo beatmap;
private Bindable<bool> preferUnicode;
private readonly OsuSpriteText title;
private readonly OsuSpriteText artist;
private readonly OsuSpriteText versionMapper;
@@ -323,20 +320,14 @@ namespace osu.Game.Screens.Ranking
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, OsuConfigManager config)
private void load(OsuColour colours, LocalisationEngine localisation)
{
title.Colour = artist.Colour = colours.BlueDarker;
versionMapper.Colour = colours.Gray8;
versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.Metadata.Author}";
preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode);
preferUnicode.ValueChanged += unicode =>
{
title.Text = unicode ? beatmap.Metadata.TitleUnicode : beatmap.Metadata.Title;
artist.Text = unicode ? beatmap.Metadata.ArtistUnicode : beatmap.Metadata.Artist;
};
preferUnicode.TriggerChange();
title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title);
artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist);
}
}
+25 -13
View File
@@ -120,7 +120,7 @@ namespace osu.Game.Screens.Select
public void RemoveBeatmap(BeatmapSetInfo info) => removeGroup(groups.Find(b => b.BeatmapSet.ID == info.ID));
public Action<BeatmapGroup, BeatmapInfo> SelectionChanged;
public Action<BeatmapInfo> SelectionChanged;
public Action StartRequested;
@@ -230,7 +230,7 @@ namespace osu.Game.Screens.Select
return new BeatmapGroup(beatmapSet, database)
{
SelectionChanged = SelectionChanged,
SelectionChanged = (g, p) => selectGroup(g, p),
StartRequested = b => StartRequested?.Invoke(),
State = BeatmapGroupState.Collapsed
};
@@ -328,21 +328,33 @@ namespace osu.Game.Screens.Select
private void selectGroup(BeatmapGroup group, BeatmapPanel panel = null, bool animated = true)
{
if (panel == null)
panel = group.BeatmapPanels.First();
try
{
if (panel == null)
panel = group.BeatmapPanels.First();
Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group");
if (selectedPanel == panel) return;
if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden)
selectedGroup.State = BeatmapGroupState.Collapsed;
Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group");
group.State = BeatmapGroupState.Expanded;
selectedGroup = group;
panel.State = PanelSelectedState.Selected;
selectedPanel = panel;
if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden)
selectedGroup.State = BeatmapGroupState.Collapsed;
float selectedY = computeYPositions(animated);
ScrollTo(selectedY, animated);
group.State = BeatmapGroupState.Expanded;
panel.State = PanelSelectedState.Selected;
if (selectedPanel == panel) return;
selectedPanel = panel;
selectedGroup = group;
SelectionChanged?.Invoke(panel.Beatmap);
}
finally
{
float selectedY = computeYPositions(animated);
ScrollTo(selectedY, animated);
}
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+5 -5
View File
@@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select
{
beatmap = value;
Leaderboard.Beatmap = beatmap?.BeatmapInfo;
Details.Beatmap = beatmap?.Beatmap.BeatmapInfo;
Details.Beatmap = beatmap?.BeatmapInfo;
}
}
@@ -66,16 +66,16 @@ namespace osu.Game.Screens.Select
{
Details = new BeatmapDetails
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(5),
RelativeSizeAxes = Axes.X,
Masking = true,
Height = 352,
Alpha = 0,
},
Leaderboard = new Leaderboard
{
RelativeSizeAxes = Axes.Both,
}
});
}
}
}
}
@@ -4,10 +4,12 @@
using System;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
@@ -21,15 +23,22 @@ namespace osu.Game.Screens.Select
public Action<BeatmapDetailTab, bool> OnFilter; //passed the selected tab and if mods is checked
private Bindable<BeatmapDetailTab> selectedTab;
private void invokeOnFilter()
{
OnFilter?.Invoke(tabs.Current, modsCheckbox.Current);
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
private void load(OsuColour colour, OsuConfigManager config)
{
modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight;
selectedTab = config.GetBindable<BeatmapDetailTab>(OsuConfig.BeatmapDetailTab);
tabs.Current.BindTo(selectedTab);
tabs.Current.TriggerChange();
}
public BeatmapDetailAreaTabControl()
@@ -62,8 +71,6 @@ namespace osu.Game.Screens.Select
tabs.Current.ValueChanged += item => invokeOnFilter();
modsCheckbox.Current.ValueChanged += item => invokeOnFilter();
tabs.Current.Value = BeatmapDetailTab.Global;
}
}
+105 -51
View File
@@ -14,6 +14,9 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using System.Globalization;
using System.Linq;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Framework.Threading;
namespace osu.Game.Screens.Select
{
@@ -39,60 +42,108 @@ namespace osu.Game.Screens.Select
private readonly BarGraph retryGraph;
private readonly BarGraph failGraph;
private ScheduledDelegate pendingBeatmapSwitch;
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get
{
return beatmap;
}
get { return beatmap; }
set
{
beatmap = value;
if (beatmap == null) return;
description.Text = beatmap.Version;
source.Text = beatmap.Metadata.Source;
tags.Text = beatmap.Metadata.Tags;
circleSize.Value = beatmap.Difficulty.CircleSize;
drainRate.Value = beatmap.Difficulty.DrainRate;
overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty;
approachRate.Value = beatmap.Difficulty.ApproachRate;
stars.Value = (float)beatmap.StarDifficulty;
if (beatmap.Metrics?.Ratings.Any() ?? false)
{
var ratings = beatmap.Metrics.Ratings.ToList();
ratingsContainer.Show();
negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString();
positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString();
ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum();
ratingsGraph.Values = ratings.Select(rating => (float)rating);
}
else
ratingsContainer.Hide();
if ((beatmap.Metrics?.Retries.Any() ?? false) && beatmap.Metrics.Fails.Any())
{
var retries = beatmap.Metrics.Retries;
var fails = beatmap.Metrics.Fails;
retryFailContainer.Show();
float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max();
failGraph.MaxValue = maxValue;
retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(fail => (float)fail);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue));
}
else
retryFailContainer.Hide();
pendingBeatmapSwitch?.Cancel();
pendingBeatmapSwitch = Schedule(updateStats);
}
}
private void updateStats()
{
if (beatmap == null) return;
description.Text = beatmap.Version;
source.Text = beatmap.Metadata.Source;
tags.Text = beatmap.Metadata.Tags;
circleSize.Value = beatmap.Difficulty.CircleSize;
drainRate.Value = beatmap.Difficulty.DrainRate;
overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty;
approachRate.Value = beatmap.Difficulty.ApproachRate;
stars.Value = (float)beatmap.StarDifficulty;
var requestedBeatmap = beatmap;
if (requestedBeatmap.Metrics == null)
{
var lookup = new GetBeatmapDetailsRequest(requestedBeatmap);
lookup.Success += res =>
{
if (beatmap != requestedBeatmap)
//the beatmap has been changed since we started the lookup.
return;
requestedBeatmap.Metrics = res;
Schedule(() => updateMetrics(res));
};
lookup.Failure += e => updateMetrics(null);
api.Queue(lookup);
}
updateMetrics(requestedBeatmap.Metrics, false);
}
/// <summary>
/// Update displayed metrics.
/// </summary>
/// <param name="metrics">New metrics to overwrite the existing display. Can be null.</param>
/// <param name="failOnMissing">Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates.</param>
private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
{
var hasRatings = metrics?.Ratings.Any() ?? false;
var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any();
if (hasRatings)
{
var ratings = metrics.Ratings.ToList();
ratingsContainer.Show();
negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString();
positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString();
ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum();
ratingsGraph.Values = ratings.Select(rating => (float)rating);
ratingsContainer.FadeColour(Color4.White, 500, EasingTypes.Out);
}
else if (failOnMissing)
ratingsGraph.Values = new float[10];
else
ratingsContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out);
if (hasRetriesFails)
{
var retries = metrics.Retries;
var fails = metrics.Fails;
retryFailContainer.Show();
float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max();
failGraph.MaxValue = maxValue;
retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(fail => (float)fail);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue));
retryFailContainer.FadeColour(Color4.White, 500, EasingTypes.Out);
}
else if (failOnMissing)
{
failGraph.Values = new float[100];
retryGraph.Values = new float[100];
}
else
retryFailContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out);
}
public BeatmapDetails()
{
Children = new Drawable[]
@@ -113,7 +164,6 @@ namespace osu.Game.Screens.Select
Direction = FillDirection.Vertical,
LayoutDuration = 200,
LayoutEasing = EasingTypes.OutQuint,
Padding = new MarginPadding(10) { Top = 25 },
Children = new []
{
description = new MetadataSegment("Description"),
@@ -148,8 +198,8 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0,10),
Padding = new MarginPadding(15) { Top = 25 },
Spacing = new Vector2(0,5),
Padding = new MarginPadding(10),
Children = new []
{
circleSize = new DifficultyRow("Circle Size", 7),
@@ -252,7 +302,7 @@ namespace osu.Game.Screens.Select
new Container<BarGraph>
{
RelativeSizeAxes = Axes.X,
Size = new Vector2(1/0.6f, 50),
Size = new Vector2(1 / 0.6f, 50),
Children = new[]
{
retryGraph = new BarGraph
@@ -272,9 +322,13 @@ namespace osu.Game.Screens.Select
};
}
private APIAccess api;
[BackgroundDependencyLoader]
private void load(OsuColour colour)
private void load(OsuColour colour, APIAccess api)
{
this.api = api;
description.AccentColour = colour.GrayB;
source.AccentColour = colour.GrayB;
tags.AccentColour = colour.YellowLight;
@@ -308,7 +362,7 @@ namespace osu.Game.Screens.Select
{
difficultyValue = value;
bar.Length = value / maxValue;
valueText.Text = value.ToString(CultureInfo.InvariantCulture);
valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture);
}
}
@@ -431,4 +485,4 @@ namespace osu.Game.Screens.Select
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More