mirror of
https://github.com/osukey/osukey.git
synced 2025-05-29 17:37:23 +09:00
Merge remote-tracking branch 'origin/master' into mania-stage
This commit is contained in:
commit
27e16b8cfd
@ -1,8 +1,6 @@
|
|||||||
# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu)
|
# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
|
||||||
|
|
||||||
[osu! on the web](https://osu.ppy.sh) | [dev chat](https://discord.gg/ppy)
|
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era!
|
||||||
|
|
||||||
Rhythm is just a *click* away. The future of osu! and the beginning of an open era!
|
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit fc93e11439b8b391d9e01e208368d96ba85bfa26
|
Subproject commit 72525837cf68aa9967cfede6fbe95489ed3b05ec
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Screens.Tournament;
|
using osu.Game.Screens.Tournament;
|
||||||
using osu.Game.Screens.Tournament.Teams;
|
using osu.Game.Screens.Tournament.Teams;
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Desktop.VisualTests.Tests
|
namespace osu.Desktop.VisualTests.Tests
|
||||||
{
|
{
|
||||||
@ -25,57 +24,57 @@ namespace osu.Desktop.VisualTests.Tests
|
|||||||
|
|
||||||
private class TestTeamList : ITeamList
|
private class TestTeamList : ITeamList
|
||||||
{
|
{
|
||||||
public IEnumerable<Country> Teams { get; } = new[]
|
public IEnumerable<DrawingsTeam> Teams { get; } = new[]
|
||||||
{
|
{
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "GB",
|
FlagName = "GB",
|
||||||
FullName = "United Kingdom",
|
FullName = "United Kingdom",
|
||||||
Acronym = "UK"
|
Acronym = "UK"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "FR",
|
FlagName = "FR",
|
||||||
FullName = "France",
|
FullName = "France",
|
||||||
Acronym = "FRA"
|
Acronym = "FRA"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "CN",
|
FlagName = "CN",
|
||||||
FullName = "China",
|
FullName = "China",
|
||||||
Acronym = "CHN"
|
Acronym = "CHN"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "AU",
|
FlagName = "AU",
|
||||||
FullName = "Australia",
|
FullName = "Australia",
|
||||||
Acronym = "AUS"
|
Acronym = "AUS"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "JP",
|
FlagName = "JP",
|
||||||
FullName = "Japan",
|
FullName = "Japan",
|
||||||
Acronym = "JPN"
|
Acronym = "JPN"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "RO",
|
FlagName = "RO",
|
||||||
FullName = "Romania",
|
FullName = "Romania",
|
||||||
Acronym = "ROM"
|
Acronym = "ROM"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "IT",
|
FlagName = "IT",
|
||||||
FullName = "Italy",
|
FullName = "Italy",
|
||||||
Acronym = "PIZZA"
|
Acronym = "PIZZA"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "VE",
|
FlagName = "VE",
|
||||||
FullName = "Venezuela",
|
FullName = "Venezuela",
|
||||||
Acronym = "VNZ"
|
Acronym = "VNZ"
|
||||||
},
|
},
|
||||||
new Country
|
new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = "US",
|
FlagName = "US",
|
||||||
FullName = "United States of America",
|
FullName = "United States of America",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using System;
|
using System;
|
||||||
@ -95,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score
|
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score
|
||||||
{
|
{
|
||||||
Replay = new OsuAutoReplay(beatmap)
|
Replay = new OsuAutoGenerator(beatmap).Generate()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
332
osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
Normal file
332
osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
96
osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
Normal file
96
osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
Normal file
@ -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\Drawables\Pieces\SliderBody.cs" />
|
||||||
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
|
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
|
||||||
<Compile Include="Objects\SliderTick.cs" />
|
<Compile Include="Objects\SliderTick.cs" />
|
||||||
<Compile Include="OsuAutoReplay.cs" />
|
|
||||||
<Compile Include="OsuDifficultyCalculator.cs" />
|
<Compile Include="OsuDifficultyCalculator.cs" />
|
||||||
<Compile Include="OsuKeyConversionInputManager.cs" />
|
<Compile Include="OsuKeyConversionInputManager.cs" />
|
||||||
<Compile Include="Scoring\OsuScoreProcessor.cs" />
|
<Compile Include="Scoring\OsuScoreProcessor.cs" />
|
||||||
@ -83,6 +82,8 @@
|
|||||||
<Compile Include="Objects\Spinner.cs" />
|
<Compile Include="Objects\Spinner.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Mods\OsuMod.cs" />
|
<Compile Include="Mods\OsuMod.cs" />
|
||||||
|
<Compile Include="Replays\OsuAutoGenerator.cs" />
|
||||||
|
<Compile Include="Replays\OsuAutoGeneratorBase.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
|
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
|
||||||
@ -102,6 +103,9 @@
|
|||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Replays\" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score
|
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score
|
||||||
{
|
{
|
||||||
User = new User { Username = "mekkadosu!" },
|
User = new User { Username = "mekkadosu!" },
|
||||||
Replay = new TaikoAutoReplay(beatmap)
|
Replay = new TaikoAutoReplay(beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -9,29 +10,28 @@ using osu.Game.Rulesets.Replays;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Replays
|
namespace osu.Game.Rulesets.Taiko.Replays
|
||||||
{
|
{
|
||||||
public class TaikoAutoReplay : Replay
|
public class TaikoAutoReplay : AutoGenerator<TaikoHitObject>
|
||||||
{
|
{
|
||||||
private const double swell_hit_speed = 50;
|
private const double swell_hit_speed = 50;
|
||||||
|
|
||||||
private readonly Beatmap<TaikoHitObject> beatmap;
|
|
||||||
|
|
||||||
public TaikoAutoReplay(Beatmap<TaikoHitObject> beatmap)
|
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;
|
bool hitButton = true;
|
||||||
|
|
||||||
Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
|
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;
|
ReplayButtonState button;
|
||||||
|
|
||||||
@ -105,15 +105,17 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
|
|
||||||
Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None));
|
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)
|
if (waitTime > endTime)
|
||||||
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None));
|
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
hitButton = !hitButton;
|
hitButton = !hitButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Replay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,15 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new HeadlessGameHost())
|
using (HeadlessGameHost host = new HeadlessGameHost())
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
loadOsu(host);
|
||||||
|
|
||||||
var temp = prepareTempCopy(osz_path);
|
var temp = prepareTempCopy(osz_path);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
|
|
||||||
osu.Dependencies.Get<BeatmapDatabase>().Import(temp);
|
host.Dependencies.Get<BeatmapDatabase>().Import(temp);
|
||||||
|
|
||||||
ensureLoaded(osu);
|
ensureLoaded(host);
|
||||||
|
|
||||||
Assert.IsFalse(File.Exists(temp));
|
Assert.IsFalse(File.Exists(temp));
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
Assert.IsTrue(host.IsPrimaryInstance);
|
Assert.IsTrue(host.IsPrimaryInstance);
|
||||||
Assert.IsTrue(!client.IsPrimaryInstance);
|
Assert.IsTrue(!client.IsPrimaryInstance);
|
||||||
|
|
||||||
var osu = loadOsu(host);
|
loadOsu(host);
|
||||||
|
|
||||||
var temp = prepareTempCopy(osz_path);
|
var temp = prepareTempCopy(osz_path);
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
if (!importer.ImportAsync(temp).Wait(5000))
|
if (!importer.ImportAsync(temp).Wait(5000))
|
||||||
Assert.Fail(@"IPC took too long to send");
|
Assert.Fail(@"IPC took too long to send");
|
||||||
|
|
||||||
ensureLoaded(osu);
|
ensureLoaded(host);
|
||||||
|
|
||||||
Assert.IsFalse(File.Exists(temp));
|
Assert.IsFalse(File.Exists(temp));
|
||||||
}
|
}
|
||||||
@ -71,16 +71,16 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new HeadlessGameHost())
|
using (HeadlessGameHost host = new HeadlessGameHost())
|
||||||
{
|
{
|
||||||
var osu = loadOsu(host);
|
loadOsu(host);
|
||||||
|
|
||||||
var temp = prepareTempCopy(osz_path);
|
var temp = prepareTempCopy(osz_path);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
|
|
||||||
using (File.OpenRead(temp))
|
using (File.OpenRead(temp))
|
||||||
osu.Dependencies.Get<BeatmapDatabase>().Import(temp);
|
host.Dependencies.Get<BeatmapDatabase>().Import(temp);
|
||||||
|
|
||||||
ensureLoaded(osu);
|
ensureLoaded(host);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
|
|
||||||
@ -105,19 +105,19 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
|
|
||||||
//reset beatmap database (sqlite and storage backing)
|
//reset beatmap database (sqlite and storage backing)
|
||||||
osu.Dependencies.Get<RulesetDatabase>().Reset();
|
host.Dependencies.Get<RulesetDatabase>().Reset();
|
||||||
osu.Dependencies.Get<BeatmapDatabase>().Reset();
|
host.Dependencies.Get<BeatmapDatabase>().Reset();
|
||||||
|
|
||||||
return osu;
|
return osu;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureLoaded(OsuGameBase osu, int timeout = 10000)
|
private void ensureLoaded(GameHost host, int timeout = 10000)
|
||||||
{
|
{
|
||||||
IEnumerable<BeatmapSetInfo> resultSets = null;
|
IEnumerable<BeatmapSetInfo> resultSets = null;
|
||||||
|
|
||||||
Action waitAction = () =>
|
Action waitAction = () =>
|
||||||
{
|
{
|
||||||
while (!(resultSets = osu.Dependencies.Get<BeatmapDatabase>()
|
while (!(resultSets = host.Dependencies.Get<BeatmapDatabase>()
|
||||||
.Query<BeatmapSetInfo>().Where(s => s.OnlineBeatmapSetID == 241526)).Any())
|
.Query<BeatmapSetInfo>().Where(s => s.OnlineBeatmapSetID == 241526)).Any())
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
};
|
};
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
|
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
|
||||||
waitAction = () =>
|
waitAction = () =>
|
||||||
{
|
{
|
||||||
while ((resultBeatmaps = osu.Dependencies.Get<BeatmapDatabase>()
|
while ((resultBeatmaps = host.Dependencies.Get<BeatmapDatabase>()
|
||||||
.Query<BeatmapInfo>().Where(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12)
|
.Query<BeatmapInfo>().Where(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12)
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
};
|
};
|
||||||
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
@"Beatmaps did not import to the database in allocated time");
|
@"Beatmaps did not import to the database in allocated time");
|
||||||
|
|
||||||
//fetch children and check we can load from the post-storage path...
|
//fetch children and check we can load from the post-storage path...
|
||||||
var set = osu.Dependencies.Get<BeatmapDatabase>().GetChildren(resultSets.First());
|
var set = host.Dependencies.Get<BeatmapDatabase>().GetChildren(resultSets.First());
|
||||||
|
|
||||||
Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count(),
|
Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count(),
|
||||||
$@"Incorrect database beatmap count post-import ({resultBeatmaps.Count()} but should be {set.Beatmaps.Count}).");
|
$@"Incorrect database beatmap count post-import ({resultBeatmaps.Count()} but should be {set.Beatmaps.Count}).");
|
||||||
@ -153,16 +153,16 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
Assert.IsTrue(set.Beatmaps.Count > 0);
|
Assert.IsTrue(set.Beatmaps.Count > 0);
|
||||||
|
|
||||||
var beatmap = osu.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
|
var beatmap = host.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
|
||||||
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
||||||
|
|
||||||
beatmap = osu.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
|
beatmap = host.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
|
||||||
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
||||||
|
|
||||||
beatmap = osu.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
|
beatmap = host.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
|
||||||
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
||||||
|
|
||||||
beatmap = osu.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
|
beatmap = host.Dependencies.Get<BeatmapDatabase>().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
|
||||||
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using SQLite.Net.Attributes;
|
using SQLite.Net.Attributes;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -22,5 +23,15 @@ namespace osu.Game.Database
|
|||||||
public int PreviewTime { get; set; }
|
public int PreviewTime { get; set; }
|
||||||
public string AudioFile { get; set; }
|
public string AudioFile { get; set; }
|
||||||
public string BackgroundFile { get; set; }
|
public string BackgroundFile { get; set; }
|
||||||
|
|
||||||
|
public string[] SearchableTerms => new[]
|
||||||
|
{
|
||||||
|
Artist,
|
||||||
|
ArtistUnicode,
|
||||||
|
Title,
|
||||||
|
TitleUnicode,
|
||||||
|
Source,
|
||||||
|
Tags
|
||||||
|
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -123,6 +123,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
set { label.Text = value; }
|
set { label.Text = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly TextAwesome Icon;
|
||||||
|
|
||||||
private Color4? accentColour;
|
private Color4? accentColour;
|
||||||
public virtual Color4 AccentColour
|
public virtual Color4 AccentColour
|
||||||
{
|
{
|
||||||
@ -145,19 +147,19 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
Foreground.Children = new Drawable[]
|
Foreground.Children = new Drawable[]
|
||||||
{
|
{
|
||||||
label = new OsuSpriteText
|
label = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
new TextAwesome
|
Icon = new TextAwesome
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.fa_chevron_down,
|
Icon = FontAwesome.fa_chevron_down,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Margin = new MarginPadding { Right = 4 },
|
Margin = new MarginPadding { Right = 4 },
|
||||||
TextSize = 20
|
TextSize = 20
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
137
osu.Game/Overlays/Music/FilterControl.cs
Normal file
137
osu.Game/Overlays/Music/FilterControl.cs
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
osu.Game/Overlays/Music/PlaylistItem.cs
Normal file
137
osu.Game/Overlays/Music/PlaylistItem.cs
Normal 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;
|
||||||
|
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, IFilterable
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
FilterTerms = metadata.SearchableTerms;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] FilterTerms { get; private set; }
|
||||||
|
|
||||||
|
private bool matching = true;
|
||||||
|
|
||||||
|
public bool MatchingCurrentFilter
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (matching == value) return;
|
||||||
|
|
||||||
|
matching = value;
|
||||||
|
|
||||||
|
FadeTo(matching ? 1 : 0, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
osu.Game/Overlays/Music/PlaylistList.cs
Normal file
94
osu.Game/Overlays/Music/PlaylistList.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
private readonly SearchContainer search;
|
||||||
|
|
||||||
|
public void Filter(string searchTerm) => search.SearchTerm = searchTerm;
|
||||||
|
|
||||||
|
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[]
|
||||||
|
{
|
||||||
|
search = new SearchContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
items = new ItemSearchContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||||
|
{
|
||||||
|
public string[] FilterTerms => new string[] { };
|
||||||
|
public bool MatchingCurrentFilter
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
InvalidateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IFilterable> FilterableChildren => Children;
|
||||||
|
|
||||||
|
public ItemSearchContainer()
|
||||||
|
{
|
||||||
|
LayoutDuration = 200;
|
||||||
|
LayoutEasing = EasingTypes.OutQuint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
164
osu.Game/Overlays/Music/PlaylistOverlay.cs
Normal file
164
osu.Game/Overlays/Music/PlaylistOverlay.cs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// 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>();
|
||||||
|
|
||||||
|
public 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 = search => list.Filter(search),
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
list.BeatmapSets = BeatmapSets = beatmaps.GetAllWithChildren<BeatmapSetInfo>().ToList();
|
||||||
|
|
||||||
|
beatmapBacking.BindTo(game.Beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,13 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,43 +17,45 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.MathUtils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Overlays.Music;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
public class MusicController : FocusedOverlayContainer
|
public class MusicController : FocusedOverlayContainer
|
||||||
{
|
{
|
||||||
private Drawable currentBackground;
|
private const float player_height = 130;
|
||||||
private DragBar progress;
|
|
||||||
private Button playButton;
|
|
||||||
private SpriteText title, artist;
|
|
||||||
|
|
||||||
private List<BeatmapSetInfo> playList;
|
private const float transition_length = 800;
|
||||||
private readonly List<BeatmapInfo> playHistory = new List<BeatmapInfo>();
|
|
||||||
private int playListIndex;
|
|
||||||
private int playHistoryIndex = -1;
|
|
||||||
|
|
||||||
private TrackManager trackManager;
|
|
||||||
private Bindable<WorkingBeatmap> beatmapSource;
|
|
||||||
private WorkingBeatmap current;
|
|
||||||
private BeatmapDatabase beatmaps;
|
|
||||||
private LocalisationEngine localisation;
|
|
||||||
|
|
||||||
private Container dragContainer;
|
|
||||||
|
|
||||||
private const float progress_height = 10;
|
private const float progress_height = 10;
|
||||||
|
|
||||||
private const float bottom_black_area_height = 55;
|
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()
|
public MusicController()
|
||||||
{
|
{
|
||||||
Width = 400;
|
Width = 400;
|
||||||
Height = 130;
|
|
||||||
|
|
||||||
Margin = new MarginPadding(10);
|
Margin = new MarginPadding(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,229 +81,227 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGameBase game, BeatmapDatabase beatmaps, OsuColour colours, LocalisationEngine localisation)
|
private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation)
|
||||||
{
|
{
|
||||||
|
this.localisation = localisation;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
dragContainer = new Container
|
dragContainer = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Masking = true,
|
RelativeSizeAxes = Axes.X,
|
||||||
CornerRadius = 5,
|
AutoSizeAxes = Axes.Y,
|
||||||
EdgeEffect = new EdgeEffect
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Colour = Color4.Black.Opacity(40),
|
|
||||||
Radius = 5,
|
|
||||||
},
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
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,
|
RelativeSizeAxes = Axes.X,
|
||||||
Origin = Anchor.BottomCentre,
|
Y = player_height + 10,
|
||||||
Anchor = Anchor.BottomCentre,
|
},
|
||||||
Children = new Drawable[]
|
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,
|
Origin = Anchor.BottomCentre,
|
||||||
Direction = FillDirection.Horizontal,
|
Anchor = Anchor.TopCentre,
|
||||||
Spacing = new Vector2(5),
|
Position = new Vector2(0, 40),
|
||||||
Origin = Anchor.Centre,
|
TextSize = 25,
|
||||||
Anchor = Anchor.Centre,
|
Colour = Color4.White,
|
||||||
Children = new[]
|
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,
|
AutoSizeAxes = Axes.Both,
|
||||||
Icon = FontAwesome.fa_step_backward,
|
Direction = FillDirection.Horizontal,
|
||||||
},
|
Spacing = new Vector2(5),
|
||||||
playButton = new Button
|
Origin = Anchor.Centre,
|
||||||
{
|
Anchor = Anchor.Centre,
|
||||||
Scale = new Vector2(1.4f),
|
Children = new[]
|
||||||
IconScale = new Vector2(1.4f),
|
|
||||||
Action = () =>
|
|
||||||
{
|
{
|
||||||
if (current?.Track == null) return;
|
new Button
|
||||||
if (current.Track.IsRunning)
|
{
|
||||||
current.Track.Stop();
|
Action = prev,
|
||||||
else
|
Icon = FontAwesome.fa_step_backward,
|
||||||
current.Track.Start();
|
},
|
||||||
},
|
playButton = new Button
|
||||||
Icon = FontAwesome.fa_play_circle_o,
|
{
|
||||||
|
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,
|
Origin = Anchor.Centre,
|
||||||
Icon = FontAwesome.fa_step_forward,
|
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,
|
Origin = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.BottomCentre,
|
||||||
Position = new Vector2(-bottom_black_area_height / 2, 0),
|
Height = progress_height,
|
||||||
Icon = FontAwesome.fa_bars,
|
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;
|
beatmapBacking.BindTo(game.Beatmap);
|
||||||
trackManager = game.Audio.Track;
|
|
||||||
this.localisation = localisation;
|
|
||||||
|
|
||||||
beatmapSource = game.Beatmap ?? new Bindable<WorkingBeatmap>();
|
playlist.StateChanged += (c, s) => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, EasingTypes.OutQuint);
|
||||||
playList = beatmaps.GetAllWithChildren<BeatmapSetInfo>();
|
|
||||||
|
|
||||||
currentBackground = new MusicControllerBackground();
|
|
||||||
dragContainer.Add(currentBackground);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
beatmapSource.ValueChanged += workingChanged;
|
beatmapBacking.ValueChanged += beatmapChanged;
|
||||||
beatmapSource.TriggerChange();
|
beatmapBacking.TriggerChange();
|
||||||
|
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
Height = dragContainer.Height;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (pendingBeatmapSwitch != null)
|
|
||||||
{
|
|
||||||
pendingBeatmapSwitch();
|
|
||||||
pendingBeatmapSwitch = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current?.TrackLoaded ?? false)
|
if (current?.TrackLoaded ?? false)
|
||||||
{
|
{
|
||||||
progress.UpdatePosition(current.Track.Length == 0 ? 0 : (float)(current.Track.CurrentTime / current.Track.Length));
|
var track = current.Track;
|
||||||
playButton.Icon = current.Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
|
|
||||||
|
|
||||||
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
|
else
|
||||||
playButton.Icon = FontAwesome.fa_play_circle_o;
|
playButton.Icon = FontAwesome.fa_play_circle_o;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void workingChanged(WorkingBeatmap beatmap)
|
private void play()
|
||||||
{
|
{
|
||||||
progress.IsEnabled = beatmap != null;
|
var track = current?.Track;
|
||||||
if (beatmap == current) return;
|
|
||||||
bool audioEquals = current?.BeatmapInfo?.AudioEquals(beatmap?.BeatmapInfo) ?? false;
|
|
||||||
current = beatmap;
|
|
||||||
updateDisplay(current, audioEquals ? TransformDirection.None : TransformDirection.Next);
|
|
||||||
appendToHistory(current?.BeatmapInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendToHistory(BeatmapInfo beatmap)
|
if (track == null)
|
||||||
{
|
|
||||||
if (beatmap == null) return;
|
|
||||||
|
|
||||||
if (playHistoryIndex >= 0)
|
|
||||||
{
|
{
|
||||||
if (beatmap.AudioEquals(playHistory[playHistoryIndex]))
|
playlist.PlayNext();
|
||||||
return;
|
return;
|
||||||
if (playHistoryIndex < playHistory.Count - 1)
|
|
||||||
playHistory.RemoveRange(playHistoryIndex + 1, playHistory.Count - playHistoryIndex - 1);
|
|
||||||
}
|
}
|
||||||
playHistory.Insert(++playHistoryIndex, beatmap);
|
|
||||||
|
if (track.IsRunning)
|
||||||
|
track.Stop();
|
||||||
|
else
|
||||||
|
track.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prev()
|
private void prev()
|
||||||
{
|
{
|
||||||
if (playHistoryIndex > 0)
|
queuedDirection = TransformDirection.Prev;
|
||||||
play(playHistory[--playHistoryIndex], false);
|
playlist.PlayPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void next()
|
private void next()
|
||||||
{
|
{
|
||||||
if (playHistoryIndex < playHistory.Count - 1)
|
queuedDirection = TransformDirection.Next;
|
||||||
play(playHistory[++playHistoryIndex], true);
|
playlist.PlayNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkingBeatmap current;
|
||||||
|
private TransformDirection? queuedDirection;
|
||||||
|
|
||||||
|
private void beatmapChanged(WorkingBeatmap beatmap)
|
||||||
|
{
|
||||||
|
progressBar.IsEnabled = beatmap != null;
|
||||||
|
|
||||||
|
bool audioEquals = beatmapBacking.Value?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false;
|
||||||
|
|
||||||
|
TransformDirection direction;
|
||||||
|
|
||||||
|
if (audioEquals)
|
||||||
|
direction = TransformDirection.None;
|
||||||
|
else if (queuedDirection.HasValue)
|
||||||
|
{
|
||||||
|
direction = queuedDirection.Value;
|
||||||
|
queuedDirection = null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (playList.Count == 0) return;
|
//figure out the best direction based on order in playlist.
|
||||||
if (current != null && playList.Count == 1) return;
|
var last = current == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo.ID).Count();
|
||||||
//shuffle
|
var next = beatmapBacking.Value == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmapBacking.Value.BeatmapSetInfo.ID).Count();
|
||||||
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];
|
direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
|
||||||
if (playListIndex == playList.Count) playListIndex = 0;
|
|
||||||
}
|
|
||||||
while (nextToPlay.AudioEquals(current?.BeatmapInfo));
|
|
||||||
|
|
||||||
play(nextToPlay, true);
|
|
||||||
appendToHistory(nextToPlay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current = beatmapBacking.Value;
|
||||||
|
|
||||||
|
updateDisplay(beatmapBacking, direction);
|
||||||
|
queuedDirection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void play(BeatmapInfo info, bool isNext)
|
private ScheduledDelegate pendingBeatmapSwitch;
|
||||||
{
|
|
||||||
current = beatmaps.GetWorkingBeatmap(info, current);
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
trackManager.SetExclusive(current.Track);
|
|
||||||
current.Track.Start();
|
|
||||||
beatmapSource.Value = current;
|
|
||||||
}).ContinueWith(task => Schedule(task.ThrowIfFaulted), TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
updateDisplay(current, isNext ? TransformDirection.Next : TransformDirection.Prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action pendingBeatmapSwitch;
|
|
||||||
|
|
||||||
private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction)
|
private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction)
|
||||||
{
|
{
|
||||||
//we might be off-screen when this update comes in.
|
//we might be off-screen when this update comes in.
|
||||||
//rather than Scheduling, manually handle this to avoid possible memory contention.
|
//rather than Scheduling, manually handle this to avoid possible memory contention.
|
||||||
pendingBeatmapSwitch = () =>
|
pendingBeatmapSwitch?.Cancel();
|
||||||
|
|
||||||
|
pendingBeatmapSwitch = Schedule(delegate
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
@ -323,7 +321,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dragContainer.Add(new AsyncLoadWrapper(new MusicControllerBackground(beatmap)
|
playerContainer.Add(new AsyncLoadWrapper(new Background(beatmap)
|
||||||
{
|
{
|
||||||
OnLoadComplete = d =>
|
OnLoadComplete = d =>
|
||||||
{
|
{
|
||||||
@ -347,17 +345,15 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
Depth = float.MaxValue,
|
Depth = float.MaxValue,
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seek(float position)
|
private void seek(float position)
|
||||||
{
|
{
|
||||||
current?.Track?.Seek(current.Track.Length * position);
|
var track = current?.Track;
|
||||||
current?.Track?.Start();
|
track?.Seek(track.Length * position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const float transition_length = 800;
|
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
@ -374,14 +370,19 @@ namespace osu.Game.Overlays
|
|||||||
dragContainer.ScaleTo(0.9f, transition_length, EasingTypes.OutQuint);
|
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 Sprite sprite;
|
||||||
private readonly WorkingBeatmap beatmap;
|
private readonly WorkingBeatmap beatmap;
|
||||||
|
|
||||||
public MusicControllerBackground(WorkingBeatmap beatmap = null)
|
public Background(WorkingBeatmap beatmap = null)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
CacheDrawnFrameBuffer = true;
|
CacheDrawnFrameBuffer = true;
|
||||||
|
39
osu.Game/Rulesets/Replays/AutoGenerator.cs
Normal file
39
osu.Game/Rulesets/Replays/AutoGenerator.cs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
10
osu.Game/Rulesets/Replays/IAutoGenerator.cs
Normal file
10
osu.Game/Rulesets/Replays/IAutoGenerator.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,6 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
{
|
{
|
||||||
public class Replay
|
public class Replay
|
||||||
{
|
{
|
||||||
protected const double KEY_UP_DELAY = 50;
|
|
||||||
|
|
||||||
public User User;
|
public User User;
|
||||||
|
|
||||||
public List<ReplayFrame> Frames = new List<ReplayFrame>();
|
public List<ReplayFrame> Frames = new List<ReplayFrame>();
|
||||||
|
@ -22,6 +22,8 @@ using osu.Game.Screens.Tournament;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using OpenTK.Input;
|
using OpenTK.Input;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Menu
|
namespace osu.Game.Screens.Menu
|
||||||
{
|
{
|
||||||
@ -74,10 +76,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
if (!menuMusic)
|
if (!menuMusic)
|
||||||
{
|
{
|
||||||
trackManager = game.Audio.Track;
|
trackManager = game.Audio.Track;
|
||||||
int choosableBeatmapsetAmmount = beatmaps.Query<BeatmapSetInfo>().Count();
|
List<BeatmapSetInfo> choosableBeatmapSets = beatmaps.Query<BeatmapSetInfo>().ToList();
|
||||||
if (choosableBeatmapsetAmmount > 0)
|
if (choosableBeatmapSets.Count > 0)
|
||||||
{
|
{
|
||||||
song = beatmaps.GetWorkingBeatmap(beatmaps.GetWithChildren<BeatmapSetInfo>(RNG.Next(1, choosableBeatmapsetAmmount)).Beatmaps[0]);
|
song = beatmaps.GetWorkingBeatmap(beatmaps.GetWithChildren<BeatmapSetInfo>(choosableBeatmapSets[RNG.Next(0, choosableBeatmapSets.Count - 1)].ID).Beatmaps[0]);
|
||||||
Beatmap = song;
|
Beatmap = song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,6 +343,8 @@ namespace osu.Game.Screens.Select
|
|||||||
group.State = BeatmapGroupState.Expanded;
|
group.State = BeatmapGroupState.Expanded;
|
||||||
panel.State = PanelSelectedState.Selected;
|
panel.State = PanelSelectedState.Selected;
|
||||||
|
|
||||||
|
if (selectedPanel == panel) return;
|
||||||
|
|
||||||
selectedPanel = panel;
|
selectedPanel = panel;
|
||||||
selectedGroup = group;
|
selectedGroup = group;
|
||||||
|
|
||||||
|
@ -27,13 +27,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
bool match = hasCurrentMode;
|
bool match = hasCurrentMode;
|
||||||
|
|
||||||
match &= string.IsNullOrEmpty(SearchText)
|
if (!string.IsNullOrEmpty(SearchText))
|
||||||
|| (set.Metadata.Artist ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|
match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0);
|
||||||
|| (set.Metadata.ArtistUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|
|
||||||
|| (set.Metadata.Title ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|
|
||||||
|| (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|
|
||||||
|| (set.Metadata.Tags ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|
|
||||||
|| (set.Metadata.Source ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1;
|
|
||||||
|
|
||||||
switch (g.State)
|
switch (g.State)
|
||||||
{
|
{
|
||||||
|
@ -21,7 +21,7 @@ using osu.Game.Screens.Tournament.Components;
|
|||||||
using osu.Game.Screens.Tournament.Teams;
|
using osu.Game.Screens.Tournament.Teams;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Game.Users;
|
using osu.Framework.IO.Stores;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament
|
namespace osu.Game.Screens.Tournament
|
||||||
{
|
{
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
private GroupContainer groupsContainer;
|
private GroupContainer groupsContainer;
|
||||||
private OsuSpriteText fullTeamNameText;
|
private OsuSpriteText fullTeamNameText;
|
||||||
|
|
||||||
private readonly List<Country> allTeams = new List<Country>();
|
private readonly List<DrawingsTeam> allTeams = new List<DrawingsTeam>();
|
||||||
|
|
||||||
private DrawingsConfigManager drawingsConfig;
|
private DrawingsConfigManager drawingsConfig;
|
||||||
|
|
||||||
@ -47,11 +47,21 @@ namespace osu.Game.Screens.Tournament
|
|||||||
|
|
||||||
public ITeamList TeamList;
|
public ITeamList TeamList;
|
||||||
|
|
||||||
|
protected override DependencyContainer CreateLocalDependencies(DependencyContainer parent) => new DependencyContainer(parent);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures, Storage storage)
|
private void load(TextureStore textures, Storage storage, DependencyContainer dependencies)
|
||||||
{
|
{
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
|
TextureStore flagStore = new TextureStore();
|
||||||
|
// Local flag store
|
||||||
|
flagStore.AddStore(new RawTextureLoaderStore(new NamespacedResourceStore<byte[]>(new StorageBackedResourceStore(storage), "Drawings")));
|
||||||
|
// Default texture store
|
||||||
|
flagStore.AddStore(textures);
|
||||||
|
|
||||||
|
dependencies.Cache(flagStore);
|
||||||
|
|
||||||
if (TeamList == null)
|
if (TeamList == null)
|
||||||
TeamList = new StorageBackedTeamList(storage);
|
TeamList = new StorageBackedTeamList(storage);
|
||||||
|
|
||||||
@ -239,7 +249,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
reset(true);
|
reset(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTeamSelected(Country team)
|
private void onTeamSelected(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
groupsContainer.AddTeam(team);
|
groupsContainer.AddTeam(team);
|
||||||
|
|
||||||
@ -276,7 +286,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
teamsContainer.ClearTeams();
|
teamsContainer.ClearTeams();
|
||||||
allTeams.Clear();
|
allTeams.Clear();
|
||||||
|
|
||||||
foreach (Country t in TeamList.Teams)
|
foreach (DrawingsTeam t in TeamList.Teams)
|
||||||
{
|
{
|
||||||
if (groupsContainer.ContainsTeam(t.FullName))
|
if (groupsContainer.ContainsTeam(t.FullName))
|
||||||
continue;
|
continue;
|
||||||
@ -312,7 +322,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
if (line.ToUpper().StartsWith("GROUP"))
|
if (line.ToUpper().StartsWith("GROUP"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Country teamToAdd = allTeams.FirstOrDefault(t => t.FullName == line);
|
DrawingsTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName == line);
|
||||||
|
|
||||||
if (teamToAdd == null)
|
if (teamToAdd == null)
|
||||||
continue;
|
continue;
|
||||||
|
@ -13,7 +13,7 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Game.Users;
|
using osu.Game.Screens.Tournament.Teams;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament
|
namespace osu.Game.Screens.Tournament
|
||||||
{
|
{
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTeam(Country team)
|
public void AddTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
GroupTeam gt = new GroupTeam(team);
|
GroupTeam gt = new GroupTeam(team);
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
return allTeams.Any(t => t.Team.FullName == fullName);
|
return allTeams.Any(t => t.Team.FullName == fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RemoveTeam(Country team)
|
public bool RemoveTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
allTeams.RemoveAll(gt => gt.Team == team);
|
allTeams.RemoveAll(gt => gt.Team == team);
|
||||||
|
|
||||||
@ -122,12 +122,12 @@ namespace osu.Game.Screens.Tournament
|
|||||||
|
|
||||||
private class GroupTeam : Container
|
private class GroupTeam : Container
|
||||||
{
|
{
|
||||||
public readonly Country Team;
|
public readonly DrawingsTeam Team;
|
||||||
|
|
||||||
private readonly FillFlowContainer innerContainer;
|
private readonly FillFlowContainer innerContainer;
|
||||||
private readonly Sprite flagSprite;
|
private readonly Sprite flagSprite;
|
||||||
|
|
||||||
public GroupTeam(Country team)
|
public GroupTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
Team = team;
|
Team = team;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ using System.Text;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Game.Users;
|
using osu.Game.Screens.Tournament.Teams;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament
|
namespace osu.Game.Screens.Tournament
|
||||||
{
|
{
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTeam(Country team)
|
public void AddTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
if (groups[currentGroup].TeamsCount == maxTeams)
|
if (groups[currentGroup].TeamsCount == maxTeams)
|
||||||
return;
|
return;
|
||||||
|
@ -15,16 +15,16 @@ using osu.Framework.Graphics.Transforms;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Game.Users;
|
using osu.Game.Screens.Tournament.Teams;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament
|
namespace osu.Game.Screens.Tournament
|
||||||
{
|
{
|
||||||
public class ScrollingTeamContainer : Container
|
public class ScrollingTeamContainer : Container
|
||||||
{
|
{
|
||||||
public event Action OnScrollStarted;
|
public event Action OnScrollStarted;
|
||||||
public event Action<Country> OnSelected;
|
public event Action<DrawingsTeam> OnSelected;
|
||||||
|
|
||||||
private readonly List<Country> availableTeams = new List<Country>();
|
private readonly List<DrawingsTeam> availableTeams = new List<DrawingsTeam>();
|
||||||
|
|
||||||
private readonly Container tracker;
|
private readonly Container tracker;
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTeam(Country team)
|
public void AddTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
if (availableTeams.Contains(team))
|
if (availableTeams.Contains(team))
|
||||||
return;
|
return;
|
||||||
@ -169,12 +169,12 @@ namespace osu.Game.Screens.Tournament
|
|||||||
scrollState = ScrollState.Idle;
|
scrollState = ScrollState.Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTeams(IEnumerable<Country> teams)
|
public void AddTeams(IEnumerable<DrawingsTeam> teams)
|
||||||
{
|
{
|
||||||
if (teams == null)
|
if (teams == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (Country t in teams)
|
foreach (DrawingsTeam t in teams)
|
||||||
AddTeam(t);
|
AddTeam(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
scrollState = ScrollState.Idle;
|
scrollState = ScrollState.Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveTeam(Country team)
|
public void RemoveTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
availableTeams.Remove(team);
|
availableTeams.Remove(team);
|
||||||
|
|
||||||
@ -270,7 +270,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
|
|
||||||
private void addFlags()
|
private void addFlags()
|
||||||
{
|
{
|
||||||
foreach (Country t in availableTeams)
|
foreach (DrawingsTeam t in availableTeams)
|
||||||
{
|
{
|
||||||
Add(new ScrollingTeam(t)
|
Add(new ScrollingTeam(t)
|
||||||
{
|
{
|
||||||
@ -320,7 +320,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
public const float WIDTH = 58;
|
public const float WIDTH = 58;
|
||||||
public const float HEIGHT = 41;
|
public const float HEIGHT = 41;
|
||||||
|
|
||||||
public Country Team;
|
public DrawingsTeam Team;
|
||||||
|
|
||||||
private readonly Sprite flagSprite;
|
private readonly Sprite flagSprite;
|
||||||
private readonly Box outline;
|
private readonly Box outline;
|
||||||
@ -340,7 +340,7 @@ namespace osu.Game.Screens.Tournament
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScrollingTeam(Country team)
|
public ScrollingTeam(DrawingsTeam team)
|
||||||
{
|
{
|
||||||
Team = team;
|
Team = team;
|
||||||
|
|
||||||
|
23
osu.Game/Screens/Tournament/Teams/DrawingsTeam.cs
Normal file
23
osu.Game/Screens/Tournament/Teams/DrawingsTeam.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Tournament.Teams
|
||||||
|
{
|
||||||
|
public class DrawingsTeam
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this team.
|
||||||
|
/// </summary>
|
||||||
|
public string FullName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short acronym which appears in the group boxes post-selection.
|
||||||
|
/// </summary>
|
||||||
|
public string Acronym;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the file containing the flag.
|
||||||
|
/// </summary>
|
||||||
|
public string FlagName;
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,11 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament.Teams
|
namespace osu.Game.Screens.Tournament.Teams
|
||||||
{
|
{
|
||||||
public interface ITeamList
|
public interface ITeamList
|
||||||
{
|
{
|
||||||
IEnumerable<Country> Teams { get; }
|
IEnumerable<DrawingsTeam> Teams { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Tournament.Teams
|
namespace osu.Game.Screens.Tournament.Teams
|
||||||
{
|
{
|
||||||
@ -21,16 +20,16 @@ namespace osu.Game.Screens.Tournament.Teams
|
|||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Country> Teams
|
public IEnumerable<DrawingsTeam> Teams
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var teams = new List<Country>();
|
var teams = new List<DrawingsTeam>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open))
|
using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open))
|
||||||
using (StreamReader sr = new StreamReader(stream))
|
using (var sr = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
while (sr.Peek() != -1)
|
while (sr.Peek() != -1)
|
||||||
{
|
{
|
||||||
@ -53,7 +52,7 @@ namespace osu.Game.Screens.Tournament.Teams
|
|||||||
string acronym = split.Length >= 3 ? split[2].Trim() : teamName;
|
string acronym = split.Length >= 3 ? split[2].Trim() : teamName;
|
||||||
acronym = acronym.Substring(0, Math.Min(3, acronym.Length));
|
acronym = acronym.Substring(0, Math.Min(3, acronym.Length));
|
||||||
|
|
||||||
teams.Add(new Country
|
teams.Add(new DrawingsTeam
|
||||||
{
|
{
|
||||||
FlagName = flagName,
|
FlagName = flagName,
|
||||||
FullName = teamName,
|
FullName = teamName,
|
||||||
|
@ -18,11 +18,6 @@ namespace osu.Game.Users
|
|||||||
[JsonProperty(@"name")]
|
[JsonProperty(@"name")]
|
||||||
public string FullName;
|
public string FullName;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Short acronym which appears in the group boxes post-selection.
|
|
||||||
/// </summary>
|
|
||||||
public string Acronym;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Two-letter flag acronym (ISO 3166 standard)
|
/// Two-letter flag acronym (ISO 3166 standard)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -75,6 +75,9 @@
|
|||||||
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
|
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
|
||||||
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
|
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
|
||||||
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
|
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
|
||||||
|
<Compile Include="Overlays\Music\FilterControl.cs" />
|
||||||
|
<Compile Include="Overlays\Music\PlaylistItem.cs" />
|
||||||
|
<Compile Include="Overlays\Music\PlaylistList.cs" />
|
||||||
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
|
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
|
||||||
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
|
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
|
||||||
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
|
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
|
||||||
@ -322,6 +325,7 @@
|
|||||||
<Compile Include="Screens\Tournament\Teams\ITeamList.cs" />
|
<Compile Include="Screens\Tournament\Teams\ITeamList.cs" />
|
||||||
<Compile Include="Screens\Tournament\ScrollingTeamContainer.cs" />
|
<Compile Include="Screens\Tournament\ScrollingTeamContainer.cs" />
|
||||||
<Compile Include="Screens\Tournament\Teams\StorageBackedTeamList.cs" />
|
<Compile Include="Screens\Tournament\Teams\StorageBackedTeamList.cs" />
|
||||||
|
<Compile Include="Screens\Tournament\Teams\DrawingsTeam.cs" />
|
||||||
<Compile Include="Users\UpdateableAvatar.cs" />
|
<Compile Include="Users\UpdateableAvatar.cs" />
|
||||||
<Compile Include="Users\User.cs" />
|
<Compile Include="Users\User.cs" />
|
||||||
<Compile Include="Graphics\UserInterface\Volume\VolumeControl.cs" />
|
<Compile Include="Graphics\UserInterface\Volume\VolumeControl.cs" />
|
||||||
@ -421,6 +425,9 @@
|
|||||||
<Compile Include="Screens\Select\BeatmapDetailArea.cs" />
|
<Compile Include="Screens\Select\BeatmapDetailArea.cs" />
|
||||||
<Compile Include="Graphics\UserInterface\OsuTabControlCheckbox.cs" />
|
<Compile Include="Graphics\UserInterface\OsuTabControlCheckbox.cs" />
|
||||||
<Compile Include="Screens\Select\BeatmapDetailAreaTabControl.cs" />
|
<Compile Include="Screens\Select\BeatmapDetailAreaTabControl.cs" />
|
||||||
|
<Compile Include="Overlays\Music\PlaylistOverlay.cs" />
|
||||||
|
<Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
|
||||||
|
<Compile Include="Rulesets\Replays\AutoGenerator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">
|
<ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user