mirror of
https://github.com/ppy/osu.git
synced 2025-03-25 03:07:34 +08:00
Merge https://github.com/ppy/osu into channel-selection
This commit is contained in:
commit
64016f9466
@ -1 +1 @@
|
||||
Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f
|
||||
Subproject commit 8baad1b9484b9f35724e2f965c18cfe710907d80
|
@ -1 +1 @@
|
||||
Subproject commit ffccbeb98dc9e8f0965520270b5885e63f244c83
|
||||
Subproject commit 9f46a456dc3a56dcbff09671a3f588b16a464106
|
199
osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
Normal file
199
osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
Normal file
@ -0,0 +1,199 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
public class TestCaseDirect : TestCase
|
||||
{
|
||||
public override string Description => @"osu!direct overlay";
|
||||
|
||||
private DirectOverlay direct;
|
||||
private RulesetDatabase rulesets;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
Add(direct = new DirectOverlay());
|
||||
newBeatmaps();
|
||||
|
||||
AddStep(@"toggle", direct.ToggleVisibility);
|
||||
AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetDatabase rulesets)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
private void newBeatmaps()
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(0);
|
||||
|
||||
direct.BeatmapSets = new[]
|
||||
{
|
||||
new BeatmapSetInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"OrVid",
|
||||
Artist = @"An",
|
||||
Author = @"RLC",
|
||||
Source = @"",
|
||||
},
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 5.35f,
|
||||
Metadata = new BeatmapMetadata(),
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
Covers = new[] { @"https://assets.ppy.sh//beatmaps/578332/covers/cover.jpg?1494591390" },
|
||||
Preview = @"https://b.ppy.sh/preview/578332.mp3",
|
||||
PlayCount = 97,
|
||||
FavouriteCount = 72,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new BeatmapSetInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"tiny lamp",
|
||||
Artist = @"fhana",
|
||||
Author = @"Sotarks",
|
||||
Source = @"ぎんぎつね",
|
||||
},
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 5.81f,
|
||||
Metadata = new BeatmapMetadata(),
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
Covers = new[] { @"https://assets.ppy.sh//beatmaps/599627/covers/cover.jpg?1494539318" },
|
||||
Preview = @"https//b.ppy.sh/preview/599627.mp3",
|
||||
PlayCount = 3082,
|
||||
FavouriteCount = 14,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new BeatmapSetInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"At Gwanghwamun",
|
||||
Artist = @"KYUHYUN",
|
||||
Author = @"Cerulean Veyron",
|
||||
Source = @"",
|
||||
},
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 0.9f,
|
||||
Metadata = new BeatmapMetadata(),
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
Covers = new[] { @"https://assets.ppy.sh//beatmaps/513268/covers/cover.jpg?1494502863" },
|
||||
Preview = @"https//b.ppy.sh/preview/513268.mp3",
|
||||
PlayCount = 2762,
|
||||
FavouriteCount = 15,
|
||||
},
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 1.1f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 2.02f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 3.49f,
|
||||
},
|
||||
},
|
||||
},
|
||||
new BeatmapSetInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"RHAPSODY OF BLUE SKY",
|
||||
Artist = @"fhana",
|
||||
Author = @"[Kamiya]",
|
||||
Source = @"小林さんちのメイドラゴン",
|
||||
},
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 1.26f,
|
||||
Metadata = new BeatmapMetadata(),
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
Covers = new[] { @"https://assets.ppy.sh//beatmaps/586841/covers/cover.jpg?1494052741" },
|
||||
Preview = @"https//b.ppy.sh/preview/586841.mp3",
|
||||
PlayCount = 62317,
|
||||
FavouriteCount = 161,
|
||||
},
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 2.01f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 2.87f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 3.76f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 3.93f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 4.37f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 5.13f,
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 5.42f,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
74
osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
Normal file
74
osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
internal class TestCaseDrawableRoom : TestCase
|
||||
{
|
||||
public override string Description => @"Select your favourite room";
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
DrawableRoom first;
|
||||
DrawableRoom second;
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 500f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
first = new DrawableRoom(new Room()),
|
||||
second = new DrawableRoom(new Room()),
|
||||
}
|
||||
});
|
||||
|
||||
first.Room.Name.Value = @"Great Room Right Here";
|
||||
first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }};
|
||||
first.Room.Status.Value = new RoomStatusOpen();
|
||||
first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" };
|
||||
|
||||
second.Room.Name.Value = @"Relax It's The Weekend";
|
||||
second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }};
|
||||
second.Room.Status.Value = new RoomStatusPlaying();
|
||||
second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" };
|
||||
|
||||
AddStep(@"change state", () =>
|
||||
{
|
||||
first.Room.Status.Value = new RoomStatusPlaying();
|
||||
});
|
||||
|
||||
AddStep(@"change name", () =>
|
||||
{
|
||||
first.Room.Name.Value = @"I Changed Name";
|
||||
});
|
||||
|
||||
AddStep(@"change host", () =>
|
||||
{
|
||||
first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } };
|
||||
});
|
||||
|
||||
AddStep(@"change beatmap", () =>
|
||||
{
|
||||
first.Room.Beatmap.Value = null;
|
||||
});
|
||||
|
||||
AddStep(@"change state", () =>
|
||||
{
|
||||
first.Room.Status.Value = new RoomStatusOpen();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.UI;
|
||||
using System.Collections.Generic;
|
||||
using osu.Desktop.VisualTests.Beatmaps;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
@ -53,8 +53,8 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
time += RNG.Next(50, 500);
|
||||
}
|
||||
|
||||
TimingInfo timing = new TimingInfo();
|
||||
timing.ControlPoints.Add(new ControlPoint
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint
|
||||
{
|
||||
BeatLength = 200
|
||||
});
|
||||
@ -73,7 +73,7 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Author = @"peppy",
|
||||
},
|
||||
},
|
||||
TimingInfo = timing
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
Add(new Drawable[]
|
||||
|
@ -7,10 +7,10 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using OpenTK;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Timing;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
@ -27,7 +27,7 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
|
||||
{
|
||||
Clear();
|
||||
Add(new ManiaPlayfield(cols, new List<ControlPoint>())
|
||||
Add(new ManiaPlayfield(cols, new List<TimingChange>())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Clear();
|
||||
|
||||
ManiaPlayfield playField;
|
||||
Add(playField = new ManiaPlayfield(cols, new List<ControlPoint> { new ControlPoint { BeatLength = 200 } })
|
||||
Add(playField = new ManiaPlayfield(cols, new List<TimingChange> { new TimingChange { BeatLength = 200 } })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
57
osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
Normal file
57
osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Users;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
internal class TestCaseUserPanel : TestCase
|
||||
{
|
||||
public override string Description => @"Panels for displaying a user's status";
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
UserPanel flyte;
|
||||
UserPanel peppy;
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10f),
|
||||
Children = new[]
|
||||
{
|
||||
flyte = new UserPanel(new User
|
||||
{
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
Country = new Country { FlagName = @"JP" },
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
|
||||
}) { Width = 300 },
|
||||
peppy = new UserPanel(new User
|
||||
{
|
||||
Username = @"peppy",
|
||||
Id = 2,
|
||||
Country = new Country { FlagName = @"AU" },
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg"
|
||||
}) { Width = 300 },
|
||||
},
|
||||
});
|
||||
|
||||
flyte.Status.Value = new UserStatusOnline();
|
||||
peppy.Status.Value = new UserStatusSoloGame();
|
||||
|
||||
AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
|
||||
AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
|
||||
AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
|
||||
AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
|
||||
AddStep(@"null status", () => { flyte.Status.Value = null; });
|
||||
}
|
||||
}
|
||||
}
|
@ -220,6 +220,9 @@
|
||||
<Compile Include="Tests\TestCaseLeaderboard.cs" />
|
||||
<Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
|
||||
<Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
|
||||
<Compile Include="Tests\TestCaseDrawableRoom.cs" />
|
||||
<Compile Include="Tests\TestCaseUserPanel.cs" />
|
||||
<Compile Include="Tests\TestCaseDirect.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup />
|
||||
|
@ -37,5 +37,7 @@ namespace osu.Desktop.Beatmaps.IO
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public override Stream GetUnderlyingStream() => null;
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,18 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
|
||||
using osu.Game.Rulesets.Mania.MathUtils;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
{
|
||||
public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of previous notes to consider for density calculation.
|
||||
/// </summary>
|
||||
private const int max_notes_for_density = 7;
|
||||
|
||||
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
||||
|
||||
private Pattern lastPattern = new Pattern();
|
||||
@ -55,6 +62,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
yield return obj;
|
||||
}
|
||||
|
||||
private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
|
||||
private double density = int.MaxValue;
|
||||
private void computeDensity(double newNoteTime)
|
||||
{
|
||||
if (prevNoteTimes.Count == max_notes_for_density)
|
||||
prevNoteTimes.RemoveAt(0);
|
||||
prevNoteTimes.Add(newNoteTime);
|
||||
|
||||
density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||
}
|
||||
|
||||
private double lastTime;
|
||||
private Vector2 lastPosition;
|
||||
private PatternType lastStair;
|
||||
private void recordNote(double time, Vector2 position)
|
||||
{
|
||||
lastTime = time;
|
||||
lastPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that generates hit objects for osu!mania specific beatmaps.
|
||||
/// </summary>
|
||||
@ -83,28 +110,31 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
// Following lines currently commented out to appease resharper
|
||||
|
||||
//Patterns.PatternGenerator conversion = null;
|
||||
Patterns.PatternGenerator conversion = null;
|
||||
|
||||
if (distanceData != null)
|
||||
{
|
||||
// Slider
|
||||
}
|
||||
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
|
||||
else if (endTimeData != null)
|
||||
{
|
||||
// Spinner
|
||||
}
|
||||
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
|
||||
else if (positionData != null)
|
||||
{
|
||||
// Circle
|
||||
computeDensity(original.StartTime);
|
||||
|
||||
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair);
|
||||
|
||||
recordNote(original.StartTime, positionData.Position);
|
||||
}
|
||||
|
||||
//if (conversion == null)
|
||||
return null;
|
||||
if (conversion == null)
|
||||
return null;
|
||||
|
||||
//Pattern newPattern = conversion.Generate();
|
||||
//lastPattern = newPattern;
|
||||
Pattern newPattern = conversion.Generate();
|
||||
lastPattern = newPattern;
|
||||
|
||||
//return newPattern.HitObjects;
|
||||
var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
|
||||
lastStair = stairPatternGenerator.StairType;
|
||||
|
||||
return newPattern.HitObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -0,0 +1,487 @@
|
||||
// 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.Linq;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.MathUtils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// A pattern generator for IHasDistance hit objects.
|
||||
/// </summary>
|
||||
internal class DistanceObjectPatternGenerator : PatternGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Base osu! slider scoring distance.
|
||||
/// </summary>
|
||||
private const float osu_base_scoring_distance = 100;
|
||||
|
||||
private readonly double endTime;
|
||||
private readonly double segmentDuration;
|
||||
private readonly int repeatCount;
|
||||
|
||||
private PatternType convertType;
|
||||
|
||||
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
|
||||
: base(random, hitObject, beatmap, previousPattern)
|
||||
{
|
||||
convertType = PatternType.None;
|
||||
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
|
||||
convertType = PatternType.LowProbability;
|
||||
|
||||
var distanceData = hitObject as IHasDistance;
|
||||
var repeatsData = hitObject as IHasRepeats;
|
||||
|
||||
repeatCount = repeatsData?.RepeatCount ?? 1;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
||||
|
||||
// The true distance, accounting for any repeats
|
||||
double distance = (distanceData?.Distance ?? 0) * repeatCount;
|
||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier);
|
||||
// The duration of the osu! hit object
|
||||
double osuDuration = distance / osuVelocity;
|
||||
|
||||
endTime = hitObject.StartTime + osuDuration;
|
||||
segmentDuration = (endTime - HitObject.StartTime) / repeatCount;
|
||||
}
|
||||
|
||||
public override Pattern Generate()
|
||||
{
|
||||
if (repeatCount > 1)
|
||||
{
|
||||
if (segmentDuration <= 90)
|
||||
return generateRandomHoldNotes(HitObject.StartTime, 1);
|
||||
|
||||
if (segmentDuration <= 120)
|
||||
{
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
return generateRandomNotes(HitObject.StartTime, repeatCount + 1);
|
||||
}
|
||||
|
||||
if (segmentDuration <= 160)
|
||||
return generateStair(HitObject.StartTime);
|
||||
|
||||
if (segmentDuration <= 200 && ConversionDifficulty > 3)
|
||||
return generateRandomMultipleNotes(HitObject.StartTime);
|
||||
|
||||
double duration = endTime - HitObject.StartTime;
|
||||
if (duration >= 4000)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
|
||||
|
||||
if (segmentDuration > 400 && repeatCount < AvailableColumns - 1 - RandomStart)
|
||||
return generateTiledHoldNotes(HitObject.StartTime);
|
||||
|
||||
return generateHoldAndNormalNotes(HitObject.StartTime);
|
||||
}
|
||||
|
||||
if (segmentDuration <= 110)
|
||||
{
|
||||
if (PreviousPattern.ColumnWithObjects < AvailableColumns)
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
else
|
||||
convertType &= ~PatternType.ForceNotStack;
|
||||
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 2.5)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random hold notes that start at an span the same amount of rows.
|
||||
/// </summary>
|
||||
/// <param name="startTime">Start time of each hold note.</param>
|
||||
/// <param name="noteCount">Number of hold notes.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomHoldNotes(double startTime, int noteCount)
|
||||
{
|
||||
// - - - -
|
||||
// ■ - ■ ■
|
||||
// □ - □ □
|
||||
// ■ - ■ ■
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects;
|
||||
int nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
addToPattern(pattern, nextColumn, startTime, endTime);
|
||||
}
|
||||
|
||||
// This is can't be combined with the above loop due to RNG
|
||||
for (int i = 0; i < noteCount - usableColumns; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
addToPattern(pattern, nextColumn, startTime, endTime);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random notes, with one note per row and no stacking.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <param name="noteCount">The number of notes.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomNotes(double startTime, int noteCount)
|
||||
{
|
||||
// - - - -
|
||||
// x - - -
|
||||
// - - x -
|
||||
// - - - x
|
||||
// x - - -
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
}
|
||||
|
||||
int lastColumn = nextColumn;
|
||||
for (int i = 0; i < noteCount; i++)
|
||||
{
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
while (nextColumn == lastColumn)
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
|
||||
lastColumn = nextColumn;
|
||||
startTime += segmentDuration;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stair of notes, with one note per row.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateStair(double startTime)
|
||||
{
|
||||
// - - - -
|
||||
// x - - -
|
||||
// - x - -
|
||||
// - - x -
|
||||
// - - - x
|
||||
// - - x -
|
||||
// - x - -
|
||||
// x - - -
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
bool increasing = Random.NextDouble() > 0.5;
|
||||
|
||||
for (int i = 0; i <= repeatCount; i++)
|
||||
{
|
||||
addToPattern(pattern, column, startTime, startTime);
|
||||
startTime += segmentDuration;
|
||||
|
||||
// Check if we're at the borders of the stage, and invert the pattern if so
|
||||
if (increasing)
|
||||
{
|
||||
if (column >= AvailableColumns - 1)
|
||||
{
|
||||
increasing = false;
|
||||
column--;
|
||||
}
|
||||
else
|
||||
column++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (column <= RandomStart)
|
||||
{
|
||||
increasing = true;
|
||||
column++;
|
||||
}
|
||||
else
|
||||
column--;
|
||||
}
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random notes with 1-2 notes per row and no stacking.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomMultipleNotes(double startTime)
|
||||
{
|
||||
// - - - -
|
||||
// x - - -
|
||||
// - x x -
|
||||
// - - - x
|
||||
// x - x -
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool legacy = AvailableColumns >= 4 && AvailableColumns <= 8;
|
||||
int interval = Random.Next(1, AvailableColumns - (legacy ? 1 : 0));
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
for (int i = 0; i <= repeatCount; i++)
|
||||
{
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
|
||||
nextColumn += interval;
|
||||
if (nextColumn >= AvailableColumns - RandomStart)
|
||||
nextColumn = nextColumn - AvailableColumns - RandomStart + (legacy ? 1 : 0);
|
||||
nextColumn += RandomStart;
|
||||
|
||||
// If we're in 2K, let's not add many consecutive doubles
|
||||
if (AvailableColumns > 2)
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
startTime += segmentDuration;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random hold notes. The amount of hold notes generated is determined by probabilities.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The hold note start time.</param>
|
||||
/// <param name="p2">The probability required for 2 hold notes to be generated.</param>
|
||||
/// <param name="p3">The probability required for 3 hold notes to be generated.</param>
|
||||
/// <param name="p4">The probability required for 4 hold notes to be generated.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4)
|
||||
{
|
||||
// - - - -
|
||||
// ■ - ■ ■
|
||||
// □ - □ □
|
||||
// ■ - ■ ■
|
||||
|
||||
switch (AvailableColumns)
|
||||
{
|
||||
case 2:
|
||||
p2 = 0;
|
||||
p3 = 0;
|
||||
p4 = 0;
|
||||
break;
|
||||
case 3:
|
||||
p2 = Math.Max(p2, 0.1);
|
||||
p3 = 0;
|
||||
p4 = 0;
|
||||
break;
|
||||
case 4:
|
||||
p2 = Math.Max(p2, 0.3);
|
||||
p3 = Math.Max(p3, 0.04);
|
||||
p4 = 0;
|
||||
break;
|
||||
case 5:
|
||||
p2 = Math.Max(p2, 0.34);
|
||||
p3 = Math.Max(p3, 0.1);
|
||||
p4 = Math.Max(p4, 0.03);
|
||||
break;
|
||||
}
|
||||
|
||||
Func<SampleInfo, bool> isDoubleSample = sample => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
|
||||
|
||||
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
|
||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
||||
|
||||
if (canGenerateTwoNotes)
|
||||
p2 = 1;
|
||||
|
||||
return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates tiled hold notes. You can think of this as a stair of hold notes.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The first hold note start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateTiledHoldNotes(double startTime)
|
||||
{
|
||||
// - - - -
|
||||
// ■ ■ ■ ■
|
||||
// □ □ □ □
|
||||
// □ □ □ □
|
||||
// □ □ □ ■
|
||||
// □ □ ■ -
|
||||
// □ ■ - -
|
||||
// ■ - - -
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int columnRepeat = Math.Min(repeatCount, AvailableColumns);
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
}
|
||||
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
|
||||
addToPattern(pattern, nextColumn, startTime, endTime);
|
||||
startTime += segmentDuration;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hold note alongside normal notes.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of notes.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateHoldAndNormalNotes(double startTime)
|
||||
{
|
||||
// - - - -
|
||||
// ■ x x -
|
||||
// ■ - x x
|
||||
// ■ x - x
|
||||
// ■ - x x
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(holdColumn))
|
||||
holdColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
}
|
||||
|
||||
// Create the hold note
|
||||
addToPattern(pattern, holdColumn, startTime, endTime);
|
||||
|
||||
int noteCount = 1;
|
||||
if (ConversionDifficulty > 6.5)
|
||||
noteCount = GetRandomNoteCount(0.63, 0);
|
||||
else if (ConversionDifficulty > 4)
|
||||
noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0.12 : 0.45, 0);
|
||||
else if (ConversionDifficulty > 2.5)
|
||||
noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0 : 0.24, 0);
|
||||
noteCount = Math.Min(AvailableColumns - 1, noteCount);
|
||||
|
||||
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
|
||||
int nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
|
||||
var rowPattern = new Pattern();
|
||||
for (int i = 0; i <= repeatCount; i++)
|
||||
{
|
||||
if (!(ignoreHead && startTime == HitObject.StartTime))
|
||||
{
|
||||
for (int j = 0; j < noteCount; j++)
|
||||
{
|
||||
while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn)
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
addToPattern(rowPattern, nextColumn, startTime, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
pattern.Add(rowPattern);
|
||||
rowPattern.Clear();
|
||||
|
||||
startTime += segmentDuration;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the sample info list at a point in time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||
/// <returns></returns>
|
||||
private SampleInfoList sampleInfoListAt(double time)
|
||||
{
|
||||
var curveData = HitObject as IHasCurve;
|
||||
|
||||
if (curveData == null)
|
||||
return HitObject.Samples;
|
||||
|
||||
double segmentTime = (endTime - HitObject.StartTime) / repeatCount;
|
||||
|
||||
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
|
||||
return curveData.RepeatSamples[index];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and adds a note to a pattern.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern to add to.</param>
|
||||
/// <param name="column">The column to add the note to.</param>
|
||||
/// <param name="startTime">The start time of the note.</param>
|
||||
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
|
||||
private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
|
||||
{
|
||||
ManiaHitObject newObject;
|
||||
|
||||
if (startTime == endTime)
|
||||
{
|
||||
newObject = new Note
|
||||
{
|
||||
StartTime = startTime,
|
||||
Samples = sampleInfoListAt(startTime),
|
||||
Column = column
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = new HoldNote
|
||||
{
|
||||
StartTime = startTime,
|
||||
Samples = sampleInfoListAt(startTime),
|
||||
EndSamples = sampleInfoListAt(endTime),
|
||||
Column = column,
|
||||
Duration = endTime - startTime
|
||||
};
|
||||
}
|
||||
|
||||
pattern.Add(newObject);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.MathUtils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
internal class EndTimeObjectPatternGenerator : PatternGenerator
|
||||
{
|
||||
private readonly double endTime;
|
||||
|
||||
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap)
|
||||
: base(random, hitObject, beatmap, new Pattern())
|
||||
{
|
||||
var endtimeData = HitObject as IHasEndTime;
|
||||
|
||||
endTime = endtimeData?.EndTime ?? 0;
|
||||
}
|
||||
|
||||
public override Pattern Generate()
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool generateHold = endTime - HitObject.StartTime >= 100;
|
||||
|
||||
if (AvailableColumns == 8)
|
||||
{
|
||||
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000)
|
||||
addToPattern(pattern, 0, generateHold);
|
||||
else
|
||||
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
|
||||
}
|
||||
else if (AvailableColumns > 0)
|
||||
addToPattern(pattern, getNextRandomColumn(0), generateHold);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks a random column after a column.
|
||||
/// </summary>
|
||||
/// <param name="start">The starting column.</param>
|
||||
/// <returns>A random column after <paramref name="start"/>.</returns>
|
||||
private int getNextRandomColumn(int start)
|
||||
{
|
||||
int nextColumn = Random.Next(start, AvailableColumns);
|
||||
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(start, AvailableColumns);
|
||||
|
||||
return nextColumn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and adds a note to a pattern.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern to add to.</param>
|
||||
/// <param name="column">The column to add the note to.</param>
|
||||
/// <param name="holdNote">Whether to add a hold note.</param>
|
||||
private void addToPattern(Pattern pattern, int column, bool holdNote)
|
||||
{
|
||||
ManiaHitObject newObject;
|
||||
|
||||
if (holdNote)
|
||||
{
|
||||
newObject = new HoldNote
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
EndSamples = HitObject.Samples,
|
||||
Column = column,
|
||||
Duration = endTime - HitObject.StartTime
|
||||
};
|
||||
|
||||
newObject.Samples.Add(new SampleInfo
|
||||
{
|
||||
Name = SampleInfo.HIT_NORMAL
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = new Note
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Samples = HitObject.Samples,
|
||||
Column = column
|
||||
};
|
||||
}
|
||||
|
||||
pattern.Add(newObject);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,404 @@
|
||||
// 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.Linq;
|
||||
using OpenTK;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.MathUtils;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
internal class HitObjectPatternGenerator : PatternGenerator
|
||||
{
|
||||
public PatternType StairType { get; private set; }
|
||||
|
||||
private readonly PatternType convertType;
|
||||
|
||||
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
|
||||
: base(random, hitObject, beatmap, previousPattern)
|
||||
{
|
||||
StairType = lastStair;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime);
|
||||
|
||||
var positionData = hitObject as IHasPosition;
|
||||
|
||||
float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
|
||||
double timeSeparation = hitObject.StartTime - previousTime;
|
||||
|
||||
if (timeSeparation <= 125)
|
||||
{
|
||||
// More than 120 BPM
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
}
|
||||
|
||||
if (timeSeparation <= 80)
|
||||
{
|
||||
// More than 187 BPM
|
||||
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle;
|
||||
}
|
||||
else if (timeSeparation <= 95)
|
||||
{
|
||||
// More than 157 BPM
|
||||
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair;
|
||||
}
|
||||
else if (timeSeparation <= 105)
|
||||
{
|
||||
// More than 140 BPM
|
||||
convertType |= PatternType.ForceNotStack | PatternType.LowProbability;
|
||||
}
|
||||
else if (timeSeparation <= 125)
|
||||
{
|
||||
// More than 120 BPM
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
}
|
||||
else if (timeSeparation <= 135 && positionSeparation < 20)
|
||||
{
|
||||
// More than 111 BPM stream
|
||||
convertType |= PatternType.Cycle | PatternType.KeepSingle;
|
||||
}
|
||||
else if (timeSeparation <= 150 & positionSeparation < 20)
|
||||
{
|
||||
// More than 100 BPM stream
|
||||
convertType |= PatternType.ForceStack | PatternType.LowProbability;
|
||||
}
|
||||
else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5)
|
||||
{
|
||||
// Low density stream
|
||||
convertType |= PatternType.Reverse | PatternType.LowProbability;
|
||||
}
|
||||
else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode)
|
||||
{
|
||||
// High density
|
||||
}
|
||||
else
|
||||
convertType |= PatternType.LowProbability;
|
||||
}
|
||||
|
||||
public override Pattern Generate()
|
||||
{
|
||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||
|
||||
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by copying the last hit objects in reverse-column order
|
||||
var pattern = new Pattern();
|
||||
|
||||
for (int i = RandomStart; i < AvailableColumns; i++)
|
||||
if (PreviousPattern.ColumnHasObject(i))
|
||||
addToPattern(pattern, RandomStart + AvailableColumns - i - 1);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
|
||||
// If we convert to 7K + 1, let's not overload the special key
|
||||
&& (AvailableColumns != 8 || lastColumn != 0)
|
||||
// Make sure the last column was not the centre column
|
||||
&& (AvailableColumns % 2 == 0 || lastColumn != AvailableColumns / 2))
|
||||
{
|
||||
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
|
||||
var pattern = new Pattern();
|
||||
|
||||
int column = RandomStart + AvailableColumns - lastColumn - 1;
|
||||
addToPattern(pattern, column);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by placing on the already filled columns
|
||||
var pattern = new Pattern();
|
||||
|
||||
for (int i = RandomStart; i < AvailableColumns; i++)
|
||||
if (PreviousPattern.ColumnHasObject(i))
|
||||
addToPattern(pattern, i);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
|
||||
{
|
||||
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
|
||||
var pattern = new Pattern();
|
||||
|
||||
int targetColumn = lastColumn + 1;
|
||||
if (targetColumn == AvailableColumns)
|
||||
{
|
||||
targetColumn = RandomStart;
|
||||
StairType = PatternType.ReverseStair;
|
||||
}
|
||||
|
||||
addToPattern(pattern, targetColumn);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1)
|
||||
{
|
||||
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
|
||||
var pattern = new Pattern();
|
||||
|
||||
int targetColumn = lastColumn - 1;
|
||||
if (targetColumn == RandomStart - 1)
|
||||
{
|
||||
targetColumn = AvailableColumns - 1;
|
||||
StairType = PatternType.Stair;
|
||||
}
|
||||
|
||||
addToPattern(pattern, targetColumn);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if ((convertType & PatternType.KeepSingle) > 0)
|
||||
return generateRandomNotes(1);
|
||||
|
||||
if ((convertType & PatternType.Mirror) > 0)
|
||||
{
|
||||
if (ConversionDifficulty > 6.5)
|
||||
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||
if (ConversionDifficulty > 4)
|
||||
return generateRandomPatternWithMirrored(0.12, 0.17, 0);
|
||||
return generateRandomPatternWithMirrored(0.12, 0, 0);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||
return generateRandomPattern(1, 0.62, 0, 0);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 2)
|
||||
{
|
||||
if ((convertType & PatternType.LowProbability) > 0)
|
||||
return generateRandomPattern(0.18, 0, 0, 0);
|
||||
return generateRandomPattern(0.45, 0, 0, 0);
|
||||
}
|
||||
|
||||
return generateRandomPattern(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random notes.
|
||||
/// <para>
|
||||
/// This will generate as many as it can up to <paramref name="noteCount"/>, accounting for
|
||||
/// any stacks if <see cref="convertType"/> is forcing no stacks.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="noteCount">The amount of notes to generate.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomNotes(int noteCount)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool allowStacking = (convertType & PatternType.ForceNotStack) == 0;
|
||||
|
||||
if (!allowStacking)
|
||||
noteCount = Math.Min(noteCount, AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
for (int i = 0; i < noteCount; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking)
|
||||
{
|
||||
if ((convertType & PatternType.Gathered) > 0)
|
||||
{
|
||||
nextColumn++;
|
||||
if (nextColumn == AvailableColumns)
|
||||
nextColumn = RandomStart;
|
||||
}
|
||||
else
|
||||
nextColumn = Random.Next(RandomStart, AvailableColumns);
|
||||
}
|
||||
|
||||
addToPattern(pattern, nextColumn);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this hit object can generate a note in the special column.
|
||||
/// </summary>
|
||||
private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random pattern.
|
||||
/// </summary>
|
||||
/// <param name="p2">Probability for 2 notes to be generated.</param>
|
||||
/// <param name="p3">Probability for 3 notes to be generated.</param>
|
||||
/// <param name="p4">Probability for 4 notes to be generated.</param>
|
||||
/// <param name="p5">Probability for 5 notes to be generated.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomPattern(double p2, double p3, double p4, double p5)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5)));
|
||||
|
||||
if (RandomStart > 0 && hasSpecialColumn)
|
||||
addToPattern(pattern, 0);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random pattern which has both normal and mirrored notes.
|
||||
/// </summary>
|
||||
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
|
||||
/// <param name="p2">Probability for 2 notes to be generated.</param>
|
||||
/// <param name="p3">Probability for 3 notes to be generated.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool addToCentre;
|
||||
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
|
||||
|
||||
int columnLimit = (AvailableColumns % 2 == 0 ? AvailableColumns : AvailableColumns - 1) / 2;
|
||||
int nextColumn = Random.Next(RandomStart, columnLimit);
|
||||
for (int i = 0; i < noteCount; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, columnLimit);
|
||||
|
||||
// Add normal note
|
||||
addToPattern(pattern, nextColumn);
|
||||
// Add mirrored note
|
||||
addToPattern(pattern, RandomStart + AvailableColumns - nextColumn - 1);
|
||||
}
|
||||
|
||||
if (addToCentre)
|
||||
addToPattern(pattern, AvailableColumns / 2);
|
||||
|
||||
if (RandomStart > 0 && hasSpecialColumn)
|
||||
addToPattern(pattern, 0);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a count of notes to be generated from a list of probabilities.
|
||||
/// </summary>
|
||||
/// <param name="p2">Probability for 2 notes to be generated.</param>
|
||||
/// <param name="p3">Probability for 3 notes to be generated.</param>
|
||||
/// <param name="p4">Probability for 4 notes to be generated.</param>
|
||||
/// <param name="p5">Probability for 5 notes to be generated.</param>
|
||||
/// <returns>The amount of notes to be generated.</returns>
|
||||
private int getRandomNoteCount(double p2, double p3, double p4, double p5)
|
||||
{
|
||||
switch (AvailableColumns)
|
||||
{
|
||||
case 2:
|
||||
p2 = 0;
|
||||
p3 = 0;
|
||||
p4 = 0;
|
||||
p5 = 0;
|
||||
break;
|
||||
case 3:
|
||||
p2 = Math.Max(p2, 0.1);
|
||||
p3 = 0;
|
||||
p4 = 0;
|
||||
p5 = 0;
|
||||
break;
|
||||
case 4:
|
||||
p2 = Math.Max(p2, 0.23);
|
||||
p3 = Math.Max(p3, 0.04);
|
||||
p4 = 0;
|
||||
p5 = 0;
|
||||
break;
|
||||
case 5:
|
||||
p3 = Math.Max(p3, 0.15);
|
||||
p4 = Math.Max(p4, 0.03);
|
||||
p5 = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
|
||||
p2 = 1;
|
||||
|
||||
return GetRandomNoteCount(p2, p3, p4, p5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a count of notes to be generated from a list of probabilities.
|
||||
/// </summary>
|
||||
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
|
||||
/// <param name="p2">Probability for 2 notes to be generated.</param>
|
||||
/// <param name="p3">Probability for 3 notes to be generated.</param>
|
||||
/// <param name="addToCentre">Whether to add a note to the centre column.</param>
|
||||
/// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns>
|
||||
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
|
||||
{
|
||||
addToCentre = false;
|
||||
|
||||
if ((convertType & PatternType.ForceNotStack) > 0)
|
||||
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
switch (AvailableColumns)
|
||||
{
|
||||
case 2:
|
||||
centreProbability = 0;
|
||||
p2 = 0;
|
||||
p3 = 0;
|
||||
break;
|
||||
case 3:
|
||||
centreProbability = Math.Max(centreProbability, 0.03);
|
||||
p2 = Math.Max(p2, 0.1);
|
||||
p3 = 0;
|
||||
break;
|
||||
case 4:
|
||||
centreProbability = 0;
|
||||
p2 = Math.Max(p2 * 2, 0.2);
|
||||
p3 = 0;
|
||||
break;
|
||||
case 5:
|
||||
centreProbability = Math.Max(centreProbability, 0.03);
|
||||
p3 = 0;
|
||||
break;
|
||||
case 6:
|
||||
centreProbability = 0;
|
||||
p2 = Math.Max(p2 * 2, 0.5);
|
||||
p3 = Math.Max(p3 * 2, 0.15);
|
||||
break;
|
||||
}
|
||||
|
||||
double centreVal = Random.NextDouble();
|
||||
int noteCount = GetRandomNoteCount(p2, p3);
|
||||
|
||||
addToCentre = AvailableColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability;
|
||||
return noteCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and adds a note to a pattern.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern to add to.</param>
|
||||
/// <param name="column">The column to add the note to.</param>
|
||||
private void addToPattern(Pattern pattern, int column)
|
||||
{
|
||||
pattern.Add(new Note
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Samples = HitObject.Samples,
|
||||
Column = column
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
|
||||
|
||||
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
|
||||
drainTime -= Beatmap.EventInfo.TotalBreakTime;
|
||||
drainTime -= Beatmap.TotalBreakTime;
|
||||
|
||||
if (drainTime == 0)
|
||||
drainTime = 10000;
|
||||
|
@ -15,51 +15,51 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <summary>
|
||||
/// Keep the same as last row.
|
||||
/// </summary>
|
||||
ForceStack = 1,
|
||||
ForceStack = 1 << 0,
|
||||
/// <summary>
|
||||
/// Keep different from last row.
|
||||
/// </summary>
|
||||
ForceNotStack = 2,
|
||||
ForceNotStack = 1 << 1,
|
||||
/// <summary>
|
||||
/// Keep as single note at its original position.
|
||||
/// </summary>
|
||||
KeepSingle = 4,
|
||||
KeepSingle = 1 << 2,
|
||||
/// <summary>
|
||||
/// Use a lower random value.
|
||||
/// </summary>
|
||||
LowProbability = 8,
|
||||
LowProbability = 1 << 3,
|
||||
/// <summary>
|
||||
/// Reserved.
|
||||
/// </summary>
|
||||
Alternate = 16,
|
||||
Alternate = 1 << 4,
|
||||
/// <summary>
|
||||
/// Ignore the repeat count.
|
||||
/// </summary>
|
||||
ForceSigSlider = 32,
|
||||
ForceSigSlider = 1 << 5,
|
||||
/// <summary>
|
||||
/// Convert slider to circle.
|
||||
/// </summary>
|
||||
ForceNotSlider = 64,
|
||||
ForceNotSlider = 1 << 6,
|
||||
/// <summary>
|
||||
/// Notes gathered together.
|
||||
/// </summary>
|
||||
Gathered = 128,
|
||||
Mirror = 256,
|
||||
Gathered = 1 << 7,
|
||||
Mirror = 1 << 8,
|
||||
/// <summary>
|
||||
/// Change 0 -> 6.
|
||||
/// </summary>
|
||||
Reverse = 512,
|
||||
Reverse = 1 << 9,
|
||||
/// <summary>
|
||||
/// 1 -> 5 -> 1 -> 5 like reverse.
|
||||
/// </summary>
|
||||
Cycle = 1024,
|
||||
Cycle = 1 << 10,
|
||||
/// <summary>
|
||||
/// Next note will be at column + 1.
|
||||
/// </summary>
|
||||
Stair = 2048,
|
||||
Stair = 1 << 11,
|
||||
/// <summary>
|
||||
/// Next note will be at column - 1.
|
||||
/// </summary>
|
||||
ReverseStair = 4096
|
||||
ReverseStair = 1 << 12
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
@ -21,16 +20,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this pattern already contains a hit object in a code.
|
||||
/// Check whether a column of this patterns contains a hit object.
|
||||
/// </summary>
|
||||
/// <param name="column">The column index.</param>
|
||||
/// <returns>Whether this pattern already contains a hit object in <paramref name="column"/></returns>
|
||||
public bool IsFilled(int column) => hitObjects.Exists(h => h.Column == column);
|
||||
/// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
|
||||
public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
|
||||
|
||||
/// <summary>
|
||||
/// Amount of columns taken up by hit objects in this pattern.
|
||||
/// </summary>
|
||||
public int ColumnsFilled => HitObjects.GroupBy(h => h.Column).Count();
|
||||
public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hit object to this pattern.
|
||||
@ -42,10 +41,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
/// Copies hit object from another pattern to this one.
|
||||
/// </summary>
|
||||
/// <param name="other">The other pattern.</param>
|
||||
public void Add(Pattern other)
|
||||
{
|
||||
other.HitObjects.ForEach(Add);
|
||||
}
|
||||
public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Clears this pattern, removing all hit objects.
|
||||
|
@ -140,6 +140,26 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
||||
Miss = BeatmapDifficulty.DifficultyRange(difficulty, miss_max, miss_mid, miss_min);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hit result for a time offset.
|
||||
/// </summary>
|
||||
/// <param name="hitOffset">The time offset.</param>
|
||||
/// <returns>The hit result, or null if the time offset results in a miss.</returns>
|
||||
public ManiaHitResult? ResultFor(double hitOffset)
|
||||
{
|
||||
if (hitOffset <= Perfect / 2)
|
||||
return ManiaHitResult.Perfect;
|
||||
if (hitOffset <= Great / 2)
|
||||
return ManiaHitResult.Great;
|
||||
if (hitOffset <= Good / 2)
|
||||
return ManiaHitResult.Good;
|
||||
if (hitOffset <= Ok / 2)
|
||||
return ManiaHitResult.Ok;
|
||||
if (hitOffset <= Bad / 2)
|
||||
return ManiaHitResult.Bad;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs new hit windows which have been multiplied by a value.
|
||||
/// </summary>
|
||||
|
21
osu.Game.Rulesets.Mania/Judgements/ManiaHitResult.cs
Normal file
21
osu.Game.Rulesets.Mania/Judgements/ManiaHitResult.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Judgements
|
||||
{
|
||||
public enum ManiaHitResult
|
||||
{
|
||||
[Description("PERFECT")]
|
||||
Perfect,
|
||||
[Description("GREAT")]
|
||||
Great,
|
||||
[Description("GOOD")]
|
||||
Good,
|
||||
[Description("OK")]
|
||||
Ok,
|
||||
[Description("BAD")]
|
||||
Bad
|
||||
}
|
||||
}
|
@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
||||
public override string ResultString => string.Empty;
|
||||
|
||||
public override string MaxResultString => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The hit result.
|
||||
/// </summary>
|
||||
public ManiaHitResult ManiaResult;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Configuration;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
private readonly BodyPiece bodyPiece;
|
||||
private readonly NotePiece tailPiece;
|
||||
|
||||
public DrawableHoldNote(HoldNote hitObject)
|
||||
: base(hitObject)
|
||||
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
|
||||
: base(hitObject, key)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Height = (float)HitObject.Duration;
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Input;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -11,13 +13,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject, ManiaJudgement>
|
||||
where TObject : ManiaHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The key that will trigger input for this hit object.
|
||||
/// </summary>
|
||||
protected Bindable<Key> Key { get; private set; } = new Bindable<Key>();
|
||||
|
||||
public new TObject HitObject;
|
||||
|
||||
protected DrawableManiaHitObject(TObject hitObject)
|
||||
protected DrawableManiaHitObject(TObject hitObject, Bindable<Key> key = null)
|
||||
: base(hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
|
||||
if (key != null)
|
||||
Key.BindTo(key);
|
||||
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Y = (float)HitObject.StartTime;
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
// 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 OpenTK.Graphics;
|
||||
using OpenTK.Input;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
@ -12,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
private readonly NotePiece headPiece;
|
||||
|
||||
public DrawableNote(Note hitObject)
|
||||
: base(hitObject)
|
||||
public DrawableNote(Note hitObject, Bindable<Key> key = null)
|
||||
: base(hitObject, key)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Height = 100;
|
||||
@ -38,14 +43,53 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
protected override void CheckJudgement(bool userTriggered)
|
||||
{
|
||||
if (Time.Current > HitObject.StartTime)
|
||||
Colour = Color4.Green;
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (Judgement.TimeOffset > HitObject.HitWindows.Bad / 2)
|
||||
Judgement.Result = HitResult.Miss;
|
||||
return;
|
||||
}
|
||||
|
||||
double offset = Math.Abs(Judgement.TimeOffset);
|
||||
|
||||
if (offset > HitObject.HitWindows.Miss / 2)
|
||||
return;
|
||||
|
||||
ManiaHitResult? tmpResult = HitObject.HitWindows.ResultFor(offset);
|
||||
|
||||
if (tmpResult.HasValue)
|
||||
{
|
||||
Judgement.Result = HitResult.Hit;
|
||||
Judgement.ManiaResult = tmpResult.Value;
|
||||
}
|
||||
else
|
||||
Judgement.Result = HitResult.Miss;
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
Colour = Color4.Green;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
{
|
||||
if (Judgement.Result != HitResult.None)
|
||||
return false;
|
||||
|
||||
if (args.Key != Key)
|
||||
return false;
|
||||
|
||||
if (args.Repeat)
|
||||
return false;
|
||||
|
||||
return UpdateJudgement(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -22,14 +23,19 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
public double Duration { get; set; }
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The samples to be played when this hold note is released.
|
||||
/// </summary>
|
||||
public SampleInfoList EndSamples = new SampleInfoList();
|
||||
|
||||
/// <summary>
|
||||
/// The key-release hit windows for this hold note.
|
||||
/// </summary>
|
||||
protected HitWindows ReleaseHitWindows = new HitWindows();
|
||||
public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows();
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
ReleaseHitWindows = HitWindows * release_window_lenience;
|
||||
}
|
||||
|
@ -9,11 +9,5 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
public abstract class ManiaHitObject : HitObject, IHasColumn
|
||||
{
|
||||
public int Column { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of other <see cref="ManiaHitObject"/> that start at
|
||||
/// the same time as this hit object.
|
||||
/// </summary>
|
||||
public int Siblings { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
|
||||
@ -15,11 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// <summary>
|
||||
/// The key-press hit window for this note.
|
||||
/// </summary>
|
||||
protected HitWindows HitWindows = new HitWindows();
|
||||
public HitWindows HitWindows { get; protected set; } = new HitWindows();
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
HitWindows = new HitWindows(difficulty.OverallDifficulty);
|
||||
}
|
||||
|
@ -22,5 +22,12 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
protected override void OnNewJudgement(ManiaJudgement judgement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
Health.Value = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using OpenTK;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Timing
|
||||
{
|
||||
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Timing
|
||||
|
||||
private readonly List<DrawableControlPoint> drawableControlPoints;
|
||||
|
||||
public ControlPointContainer(IEnumerable<ControlPoint> timingChanges)
|
||||
public ControlPointContainer(IEnumerable<TimingChange> timingChanges)
|
||||
{
|
||||
drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList();
|
||||
Children = drawableControlPoints;
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Timing
|
||||
var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault();
|
||||
|
||||
if (controlPoint == null)
|
||||
throw new Exception("Could not find suitable timing section to add object to.");
|
||||
throw new InvalidOperationException("Could not find suitable timing section to add object to.");
|
||||
|
||||
controlPoint.Add(drawable);
|
||||
}
|
||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Timing
|
||||
/// </summary>
|
||||
private class DrawableControlPoint : Container
|
||||
{
|
||||
private readonly ControlPoint timingChange;
|
||||
private readonly TimingChange timingChange;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Timing
|
||||
/// the content container will scroll at twice the normal rate.
|
||||
/// </summary>
|
||||
/// <param name="timingChange">The control point to create the drawable control point for.</param>
|
||||
public DrawableControlPoint(ControlPoint timingChange)
|
||||
public DrawableControlPoint(TimingChange timingChange)
|
||||
{
|
||||
this.timingChange = timingChange;
|
||||
|
||||
|
23
osu.Game.Rulesets.Mania/Timing/TimingChange.cs
Normal file
23
osu.Game.Rulesets.Mania/Timing/TimingChange.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.Rulesets.Mania.Timing
|
||||
{
|
||||
public class TimingChange
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which this timing change happened.
|
||||
/// </summary>
|
||||
public double Time;
|
||||
|
||||
/// <summary>
|
||||
/// The beat length.
|
||||
/// </summary>
|
||||
public double BeatLength = 500;
|
||||
|
||||
/// <summary>
|
||||
/// The speed multiplier.
|
||||
/// </summary>
|
||||
public double SpeedMultiplier = 1;
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@ using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using System;
|
||||
using osu.Framework.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@ -33,7 +34,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private const float column_width = 45;
|
||||
private const float special_column_width = 70;
|
||||
|
||||
public Key Key;
|
||||
/// <summary>
|
||||
/// The key that will trigger input actions for this column and hit objects contained inside it.
|
||||
/// </summary>
|
||||
public Bindable<Key> Key = new Bindable<Key>();
|
||||
|
||||
private readonly Box background;
|
||||
private readonly Container hitTargetBar;
|
||||
@ -41,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public readonly ControlPointContainer ControlPointContainer;
|
||||
|
||||
public Column(IEnumerable<ControlPoint> timingChanges)
|
||||
public Column(IEnumerable<TimingChange> timingChanges)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = column_width;
|
||||
@ -95,6 +99,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Name = "Hit objects",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
// For column lighting, we need to capture input events before the notes
|
||||
new InputTarget
|
||||
{
|
||||
KeyDown = onKeyDown,
|
||||
KeyUp = onKeyUp
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
@ -178,12 +188,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject)
|
||||
{
|
||||
ControlPointContainer.Add(hitObject);
|
||||
}
|
||||
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject) => ControlPointContainer.Add(hitObject);
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
private bool onKeyDown(InputState state, KeyDownEventArgs args)
|
||||
{
|
||||
if (args.Repeat)
|
||||
return false;
|
||||
@ -197,7 +204,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
|
||||
private bool onKeyUp(InputState state, KeyUpEventArgs args)
|
||||
{
|
||||
if (args.Key == Key)
|
||||
{
|
||||
@ -207,5 +214,24 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a simple container which delegates various input events that have to be captured before the notes.
|
||||
/// </summary>
|
||||
private class InputTarget : Container
|
||||
{
|
||||
public Func<InputState, KeyDownEventArgs, bool> KeyDown;
|
||||
public Func<InputState, KeyUpEventArgs, bool> KeyUp;
|
||||
|
||||
public InputTarget()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
AlwaysPresent = true;
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => KeyDown?.Invoke(state, args) ?? false;
|
||||
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => KeyUp?.Invoke(state, args) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,22 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mania.Timing;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -31,22 +36,32 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield()
|
||||
{
|
||||
ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
|
||||
double lastSpeedMultiplier = 1;
|
||||
double lastBeatLength = 500;
|
||||
|
||||
if (firstTimingChange == null)
|
||||
throw new Exception("The Beatmap contains no timing points!");
|
||||
// Merge timing + difficulty points
|
||||
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
|
||||
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
|
||||
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
|
||||
|
||||
// Generate the timing points, making non-timing changes use the previous timing change
|
||||
var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
|
||||
var timingChanges = allPoints.Select(c =>
|
||||
{
|
||||
ControlPoint t = c.Clone();
|
||||
var timingPoint = c as TimingControlPoint;
|
||||
var difficultyPoint = c as DifficultyControlPoint;
|
||||
|
||||
if (c.TimingChange)
|
||||
firstTimingChange = c;
|
||||
else
|
||||
t.BeatLength = firstTimingChange.BeatLength;
|
||||
if (timingPoint != null)
|
||||
lastBeatLength = timingPoint.BeatLength;
|
||||
|
||||
return t;
|
||||
if (difficultyPoint != null)
|
||||
lastSpeedMultiplier = difficultyPoint.SpeedMultiplier;
|
||||
|
||||
return new TimingChange
|
||||
{
|
||||
Time = c.Time,
|
||||
BeatLength = lastBeatLength,
|
||||
SpeedMultiplier = lastSpeedMultiplier
|
||||
};
|
||||
});
|
||||
|
||||
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
|
||||
@ -76,13 +91,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
|
||||
{
|
||||
var maniaPlayfield = Playfield as ManiaPlayfield;
|
||||
if (maniaPlayfield == null)
|
||||
return null;
|
||||
|
||||
Bindable<Key> key = maniaPlayfield.Columns.ElementAt(h.Column).Key;
|
||||
|
||||
var holdNote = h as HoldNote;
|
||||
if (holdNote != null)
|
||||
return new DrawableHoldNote(holdNote);
|
||||
return new DrawableHoldNote(holdNote, key);
|
||||
|
||||
var note = h as Note;
|
||||
if (note != null)
|
||||
return new DrawableNote(note);
|
||||
return new DrawableNote(note, key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Timing;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.MathUtils;
|
||||
|
||||
@ -55,7 +54,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
}
|
||||
}
|
||||
|
||||
public readonly FlowContainer<Column> Columns;
|
||||
private readonly FlowContainer<Column> columns;
|
||||
public IEnumerable<Column> Columns => columns.Children;
|
||||
|
||||
private readonly ControlPointContainer barlineContainer;
|
||||
|
||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly int columnCount;
|
||||
|
||||
public ManiaPlayfield(int columnCount, IEnumerable<ControlPoint> timingChanges)
|
||||
public ManiaPlayfield(int columnCount, IEnumerable<TimingChange> timingChanges)
|
||||
{
|
||||
this.columnCount = columnCount;
|
||||
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black
|
||||
},
|
||||
Columns = new FillFlowContainer<Column>
|
||||
columns = new FillFlowContainer<Column>
|
||||
{
|
||||
Name = "Columns",
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
};
|
||||
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
Columns.Add(new Column(timingChanges));
|
||||
columns.Add(new Column(timingChanges));
|
||||
|
||||
TimeSpan = time_span_default;
|
||||
}
|
||||
@ -133,17 +133,17 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
// Set the special column + colour + key
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
Column column = Columns.Children.ElementAt(i);
|
||||
Column column = Columns.ElementAt(i);
|
||||
column.IsSpecial = isSpecialColumn(i);
|
||||
|
||||
if (!column.IsSpecial)
|
||||
continue;
|
||||
|
||||
column.Key = Key.Space;
|
||||
column.Key.Value = Key.Space;
|
||||
column.AccentColour = specialColumnColour;
|
||||
}
|
||||
|
||||
var nonSpecialColumns = Columns.Children.Where(c => !c.IsSpecial).ToList();
|
||||
var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList();
|
||||
|
||||
// We'll set the colours of the non-special columns in a separate loop, because the non-special
|
||||
// column colours are mirrored across their centre and special styles mess with this
|
||||
@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
|
||||
if (keyOffset >= 0 && keyOffset < default_keys.Length)
|
||||
column.Key = default_keys[keyOffset];
|
||||
column.Key.Value = default_keys[keyOffset];
|
||||
else
|
||||
// There is no default key defined for this column. Let's set this to Unknown for now
|
||||
// however note that this will be gone after bindings are in place
|
||||
column.Key = Key.Unknown;
|
||||
column.Key.Value = Key.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
}
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.Children.ElementAt(h.HitObject.Column).Add(h);
|
||||
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h);
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
{
|
||||
@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
|
||||
|
||||
barlineContainer.TimeSpan = value;
|
||||
Columns.Children.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
||||
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,13 +47,17 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Beatmaps\Patterns\Legacy\EndTimeObjectPatternGenerator.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\Legacy\DistanceObjectPatternGenerator.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\Legacy\HitObjectPatternGenerator.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
|
||||
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
|
||||
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
|
||||
<Compile Include="MathUtils\FastRandom.cs" />
|
||||
<Compile Include="Judgements\HitWindows.cs" />
|
||||
<Compile Include="Judgements\ManiaHitResult.cs" />
|
||||
<Compile Include="Judgements\ManiaJudgement.cs" />
|
||||
<Compile Include="ManiaDifficultyCalculator.cs" />
|
||||
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
|
||||
@ -74,6 +78,7 @@
|
||||
<Compile Include="Mods\ManiaMod.cs" />
|
||||
<Compile Include="UI\SpecialColumnPosition.cs" />
|
||||
<Compile Include="Timing\ControlPointContainer.cs" />
|
||||
<Compile Include="Timing\TimingChange.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
|
||||
|
@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private readonly TextAwesome symbol;
|
||||
|
||||
private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
|
||||
private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
|
||||
|
||||
private Color4 normalColour;
|
||||
private Color4 completeColour;
|
||||
|
||||
@ -154,13 +157,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
normalColour = colours.SpinnerBase;
|
||||
normalColour = baseColour;
|
||||
|
||||
background.AccentColour = normalColour;
|
||||
|
||||
completeColour = colours.YellowLight.Opacity(0.6f);
|
||||
completeColour = colours.YellowLight.Opacity(0.75f);
|
||||
|
||||
disc.AccentColour = colours.SpinnerFill;
|
||||
disc.AccentColour = fillColour;
|
||||
circle.Colour = colours.BlueDark;
|
||||
glow.Colour = colours.BlueDark;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -28,9 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Hollow = true,
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 14,
|
||||
Colour = value.Opacity(0.3f),
|
||||
Radius = 40,
|
||||
Colour = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
if (Complete && updateCompleteTick())
|
||||
{
|
||||
background.Flush(flushType: typeof(TransformAlpha));
|
||||
background.FadeTo(tracking_alpha + 0.4f, 60, EasingTypes.OutExpo);
|
||||
background.FadeTo(tracking_alpha + 0.2f, 60, EasingTypes.OutExpo);
|
||||
background.Delay(60);
|
||||
background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint);
|
||||
}
|
||||
|
@ -2,12 +2,10 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
@ -15,24 +13,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SpinnerTicks : Container
|
||||
{
|
||||
private Color4 glowColour;
|
||||
|
||||
public SpinnerTicks()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Anchor = Anchor.Centre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
glowColour = colours.BlueDarker.Opacity(0.4f);
|
||||
layout();
|
||||
}
|
||||
|
||||
private void layout()
|
||||
{
|
||||
const int count = 18;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
@ -44,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 20,
|
||||
Colour = glowColour,
|
||||
Radius = 10,
|
||||
Colour = Color4.Gray.Opacity(0.2f),
|
||||
},
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Masking = true,
|
||||
|
@ -6,8 +6,8 @@ using OpenTK;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
return OsuScoreResult.Miss;
|
||||
}
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -10,6 +9,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Database;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
@ -62,13 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
public double Velocity;
|
||||
public double TickDistance;
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime);
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
||||
|
||||
Velocity = scoringDistance / timing.BeatLengthAt(StartTime);
|
||||
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
|
||||
|
||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
@ -19,11 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public override bool NewCombo => true;
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
|
||||
|
||||
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
|
||||
SpinsRequired = (int)(SpinsRequired * 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Database;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
@ -77,8 +78,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
int repeats = repeatsData?.RepeatCount ?? 1;
|
||||
|
||||
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime);
|
||||
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment;
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
||||
|
||||
double speedAdjustment = difficultyPoint.SpeedMultiplier;
|
||||
double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment;
|
||||
|
||||
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
||||
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
|
||||
|
@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Framework.Audio.Track;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
{
|
||||
@ -22,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
|
||||
public const float SYMBOL_BORDER = 8;
|
||||
public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
|
||||
private const double pre_beat_transition_time = 80;
|
||||
|
||||
/// <summary>
|
||||
/// The colour of the inner circle and outer glows.
|
||||
@ -63,6 +66,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
|
||||
public CirclePiece(bool isStrong = false)
|
||||
{
|
||||
EarlyActivationMilliseconds = pre_beat_transition_time;
|
||||
|
||||
AddInternal(new Drawable[]
|
||||
{
|
||||
background = new CircularContainer
|
||||
@ -139,14 +144,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
Content.Width = 1 / Content.Scale.X;
|
||||
}
|
||||
|
||||
private const float edge_alpha_kiai = 0.5f;
|
||||
|
||||
private void resetEdgeEffects()
|
||||
{
|
||||
background.EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = AccentColour,
|
||||
Radius = KiaiMode ? 50 : 8
|
||||
Colour = AccentColour.Opacity(KiaiMode ? edge_alpha_kiai : 1f),
|
||||
Radius = KiaiMode ? 32 : 8
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||
{
|
||||
if (!effectPoint.KiaiMode)
|
||||
return;
|
||||
|
||||
if (beatIndex % (int)timingPoint.TimeSignature != 0)
|
||||
return;
|
||||
|
||||
double duration = timingPoint.BeatLength * 2;
|
||||
|
||||
background.FadeEdgeEffectTo(1, pre_beat_transition_time, EasingTypes.OutQuint);
|
||||
using (background.BeginDelayedSequence(pre_beat_transition_time))
|
||||
background.FadeEdgeEffectTo(edge_alpha_kiai, duration, EasingTypes.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
{
|
||||
public class TaikoPiece : Container, IHasAccentColour
|
||||
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
|
||||
{
|
||||
private Color4 accentColour;
|
||||
/// <summary>
|
||||
@ -17,10 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
public virtual Color4 AccentColour
|
||||
{
|
||||
get { return accentColour; }
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
}
|
||||
set { accentColour = value; }
|
||||
}
|
||||
|
||||
private bool kiaiMode;
|
||||
|
@ -5,9 +5,9 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
@ -55,11 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
private double tickSpacing = 100;
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
tickSpacing = timing.BeatLengthAt(StartTime) / TickRate;
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
|
||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||
|
||||
RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
|
||||
RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public double HitWindowMiss = 95;
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
|
||||
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// <summary>
|
||||
/// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
|
||||
/// </summary>
|
||||
public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f;
|
||||
public const float PLAYFIELD_RELATIVE_DIAMETER = 0.45f;
|
||||
|
||||
/// <summary>
|
||||
/// Scale multiplier for a strong circle.
|
||||
/// </summary>
|
||||
public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f;
|
||||
public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Default circle diameter.
|
||||
@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
|
||||
|
||||
/// <summary>
|
||||
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.
|
||||
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="TimingControlPoint.BeatLength"/> of 1000ms.
|
||||
/// </summary>
|
||||
private const double scroll_time = 6000;
|
||||
|
||||
/// <summary>
|
||||
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="ControlPoint.BeatLength"/> and other speed multipliers.
|
||||
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="TimingControlPoint.BeatLength"/> and other speed multipliers.
|
||||
/// </summary>
|
||||
public double ScrollTime;
|
||||
|
||||
@ -51,17 +51,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public bool Kiai { get; protected set; }
|
||||
|
||||
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaults(timing, difficulty);
|
||||
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||
|
||||
ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier;
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
||||
EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
|
||||
|
||||
ControlPoint overridePoint;
|
||||
Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;
|
||||
ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier;
|
||||
|
||||
if (overridePoint != null)
|
||||
Kiai |= overridePoint.KiaiMode;
|
||||
Kiai |= effectPoint.KiaiMode;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,22 +18,23 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// </summary>
|
||||
internal class HitExplosion : CircularContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The judgement this hit explosion visualises.
|
||||
/// </summary>
|
||||
public readonly TaikoJudgement Judgement;
|
||||
|
||||
private readonly Box innerFill;
|
||||
|
||||
public HitExplosion(TaikoJudgement judgement)
|
||||
{
|
||||
Judgement = judgement;
|
||||
private readonly bool isRim;
|
||||
|
||||
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
|
||||
public HitExplosion(TaikoJudgement judgement, bool isRim)
|
||||
{
|
||||
this.isRim = isRim;
|
||||
|
||||
Judgement = judgement;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
|
||||
|
||||
RelativePositionAxes = Axes.Both;
|
||||
|
||||
BorderColour = Color4.White;
|
||||
@ -54,22 +55,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
switch (Judgement.TaikoResult)
|
||||
{
|
||||
case TaikoHitResult.Good:
|
||||
innerFill.Colour = colours.Green;
|
||||
break;
|
||||
case TaikoHitResult.Great:
|
||||
innerFill.Colour = colours.Blue;
|
||||
break;
|
||||
}
|
||||
innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScaleTo(5f, 1000, EasingTypes.OutQuint);
|
||||
ScaleTo(3f, 1000, EasingTypes.OutQuint);
|
||||
FadeOut(500);
|
||||
|
||||
Expire();
|
||||
@ -80,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// </summary>
|
||||
public void VisualiseSecondHit()
|
||||
{
|
||||
ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50);
|
||||
ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
68
osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
Normal file
68
osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
Normal file
@ -0,0 +1,68 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class KiaiHitExplosion : CircularContainer
|
||||
{
|
||||
public readonly TaikoJudgement Judgement;
|
||||
|
||||
private readonly bool isRim;
|
||||
|
||||
public KiaiHitExplosion(TaikoJudgement judgement, bool isRim)
|
||||
{
|
||||
this.isRim = isRim;
|
||||
|
||||
Judgement = judgement;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER, 1);
|
||||
|
||||
Masking = true;
|
||||
Alpha = 0.25f;
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = isRim ? colours.BlueDarker : colours.PinkDarker,
|
||||
Radius = 60,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScaleTo(new Vector2(1, 3f), 500, EasingTypes.OutQuint);
|
||||
FadeOut(250);
|
||||
|
||||
Expire();
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using OpenTK;
|
||||
using osu.Game.Rulesets.Beatmaps;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
|
||||
double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
|
||||
|
||||
var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange);
|
||||
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
|
||||
|
||||
if (timingPoints.Count == 0)
|
||||
return;
|
||||
@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
StartTime = time,
|
||||
};
|
||||
|
||||
barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
|
||||
barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
|
||||
|
||||
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
|
||||
taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
|
||||
|
@ -24,12 +24,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// <summary>
|
||||
/// The default play field height.
|
||||
/// </summary>
|
||||
public const float DEFAULT_PLAYFIELD_HEIGHT = 168f;
|
||||
public const float DEFAULT_PLAYFIELD_HEIGHT = 178f;
|
||||
|
||||
/// <summary>
|
||||
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
|
||||
/// </summary>
|
||||
private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
|
||||
public const float HIT_TARGET_OFFSET = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the left area of the playfield. This area contains the input drum.
|
||||
@ -39,15 +39,18 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
protected override Container<Drawable> Content => hitObjectContainer;
|
||||
|
||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
|
||||
private readonly Container<DrawableBarLine> barLineContainer;
|
||||
private readonly Container<DrawableTaikoJudgement> judgementContainer;
|
||||
|
||||
private readonly Container hitObjectContainer;
|
||||
private readonly Container topLevelHitContainer;
|
||||
private readonly Container leftBackgroundContainer;
|
||||
private readonly Container rightBackgroundContainer;
|
||||
private readonly Box leftBackground;
|
||||
private readonly Box rightBackground;
|
||||
|
||||
private readonly Container overlayBackgroundContainer;
|
||||
private readonly Container backgroundContainer;
|
||||
|
||||
private readonly Box overlayBackground;
|
||||
private readonly Box background;
|
||||
|
||||
public TaikoPlayfield()
|
||||
{
|
||||
@ -59,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Height = DEFAULT_PLAYFIELD_HEIGHT,
|
||||
Children = new[]
|
||||
{
|
||||
rightBackgroundContainer = new Container
|
||||
backgroundContainer = new Container
|
||||
{
|
||||
Name = "Transparent playfield background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -73,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rightBackground = new Box
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.6f
|
||||
@ -82,24 +85,23 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = "Transparent playfield elements",
|
||||
Name = "Right area",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = left_area_size },
|
||||
Margin = new MarginPadding { Left = left_area_size },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Name = "Hit target container",
|
||||
X = hit_target_offset,
|
||||
Name = "Masked elements",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitExplosionContainer = new Container<HitExplosion>
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
BlendingMode = BlendingMode.Additive
|
||||
BlendingMode = BlendingMode.Additive,
|
||||
},
|
||||
barLineContainer = new Container<DrawableBarLine>
|
||||
{
|
||||
@ -114,23 +116,32 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
judgementContainer = new Container<DrawableTaikoJudgement>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
BlendingMode = BlendingMode.Additive
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
kiaiExplosionContainer = new Container<KiaiHitExplosion>
|
||||
{
|
||||
Name = "Kiai hit explosions",
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
|
||||
BlendingMode = BlendingMode.Additive
|
||||
},
|
||||
judgementContainer = new Container<DrawableTaikoJudgement>
|
||||
{
|
||||
Name = "Judgements",
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
|
||||
BlendingMode = BlendingMode.Additive
|
||||
},
|
||||
}
|
||||
},
|
||||
leftBackgroundContainer = new Container
|
||||
overlayBackgroundContainer = new Container
|
||||
{
|
||||
Name = "Left overlay",
|
||||
Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
|
||||
BorderThickness = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftBackground = new Box
|
||||
overlayBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
@ -164,11 +175,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
leftBackgroundContainer.BorderColour = colours.Gray0;
|
||||
leftBackground.Colour = colours.Gray1;
|
||||
overlayBackgroundContainer.BorderColour = colours.Gray0;
|
||||
overlayBackground.Colour = colours.Gray1;
|
||||
|
||||
rightBackgroundContainer.BorderColour = colours.Gray1;
|
||||
rightBackground.Colour = colours.Gray0;
|
||||
backgroundContainer.BorderColour = colours.Gray1;
|
||||
background.Colour = colours.Gray0;
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject<TaikoHitObject, TaikoJudgement> h)
|
||||
@ -204,6 +215,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
if (!wasHit)
|
||||
return;
|
||||
|
||||
bool isRim = judgedObject.HitObject is RimHit;
|
||||
|
||||
if (!secondHit)
|
||||
{
|
||||
if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell))
|
||||
@ -212,7 +225,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
topLevelHitContainer.Add(judgedObject.CreateProxy());
|
||||
}
|
||||
|
||||
hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement));
|
||||
hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement, isRim));
|
||||
|
||||
if (judgedObject.HitObject.Kiai)
|
||||
kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject.Judgement, isRim));
|
||||
|
||||
}
|
||||
else
|
||||
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
|
||||
@ -221,7 +238,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// <summary>
|
||||
/// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
|
||||
/// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
|
||||
///
|
||||
///
|
||||
/// <para>
|
||||
/// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
|
||||
/// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
|
||||
|
@ -87,6 +87,7 @@
|
||||
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
|
||||
<Compile Include="UI\HitTarget.cs" />
|
||||
<Compile Include="UI\InputDrum.cs" />
|
||||
<Compile Include="UI\KiaiHitExplosion.cs" />
|
||||
<Compile Include="UI\DrawableTaikoJudgement.cs" />
|
||||
<Compile Include="UI\HitExplosion.cs" />
|
||||
<Compile Include="UI\TaikoHitRenderer.cs" />
|
||||
|
@ -2,11 +2,12 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -17,8 +18,8 @@ namespace osu.Game.Beatmaps
|
||||
where T : HitObject
|
||||
{
|
||||
public BeatmapInfo BeatmapInfo;
|
||||
public TimingInfo TimingInfo = new TimingInfo();
|
||||
public EventInfo EventInfo = new EventInfo();
|
||||
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
|
||||
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
|
||||
public readonly List<Color4> ComboColors = new List<Color4>
|
||||
{
|
||||
new Color4(17, 136, 170, 255),
|
||||
@ -34,6 +35,11 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public List<T> HitObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of break time in the beatmap.
|
||||
/// </summary>
|
||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new beatmap.
|
||||
/// </summary>
|
||||
@ -41,8 +47,8 @@ namespace osu.Game.Beatmaps
|
||||
public Beatmap(Beatmap original = null)
|
||||
{
|
||||
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
|
||||
TimingInfo = original?.TimingInfo ?? TimingInfo;
|
||||
EventInfo = original?.EventInfo ?? EventInfo;
|
||||
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
|
||||
Breaks = original?.Breaks ?? Breaks;
|
||||
ComboColors = original?.ComboColors ?? ComboColors;
|
||||
}
|
||||
}
|
||||
|
17
osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
Normal file
17
osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class ControlPoint : IComparable<ControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
/// </summary>
|
||||
public double Time;
|
||||
|
||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||
}
|
||||
}
|
107
osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
Normal file
107
osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
Normal file
@ -0,0 +1,107 @@
|
||||
// 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 osu.Framework.Lists;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class ControlPointInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// All timing points.
|
||||
/// </summary>
|
||||
public readonly SortedList<TimingControlPoint> TimingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
|
||||
|
||||
/// <summary>
|
||||
/// All difficulty points.
|
||||
/// </summary>
|
||||
public readonly SortedList<DifficultyControlPoint> DifficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
|
||||
|
||||
/// <summary>
|
||||
/// All sound points.
|
||||
/// </summary>
|
||||
public readonly SortedList<SoundControlPoint> SoundPoints = new SortedList<SoundControlPoint>(Comparer<SoundControlPoint>.Default);
|
||||
|
||||
/// <summary>
|
||||
/// All effect points.
|
||||
/// </summary>
|
||||
public readonly SortedList<EffectControlPoint> EffectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the difficulty control point at.</param>
|
||||
/// <returns>The difficulty control point.</returns>
|
||||
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the effect control point at.</param>
|
||||
/// <returns>The effect control point.</returns>
|
||||
public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the sound control point at.</param>
|
||||
/// <returns>The sound control point.</returns>
|
||||
public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the timing control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the timing control point at.</param>
|
||||
/// <returns>The timing control point.</returns>
|
||||
public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
|
||||
|
||||
/// <summary>
|
||||
/// Finds the maximum BPM represented by any timing control point.
|
||||
/// </summary>
|
||||
public double BPMMaximum =>
|
||||
60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the minimum BPM represented by any timing control point.
|
||||
/// </summary>
|
||||
public double BPMMinimum =>
|
||||
60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the mode BPM (most common BPM) represented by the control points.
|
||||
/// </summary>
|
||||
public double BPMMode =>
|
||||
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
||||
|
||||
/// <summary>
|
||||
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="list">The list to search.</param>
|
||||
/// <param name="time">The time to find the control point at.</param>
|
||||
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
|
||||
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
||||
private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
|
||||
where T : ControlPoint, new()
|
||||
{
|
||||
if (list.Count == 0)
|
||||
return new T();
|
||||
|
||||
if (time < list[0].Time)
|
||||
return prePoint ?? new T();
|
||||
|
||||
int index = list.BinarySearch(new T() { Time = time });
|
||||
|
||||
// Check if we've found an exact match (t == time)
|
||||
if (index >= 0)
|
||||
return list[index];
|
||||
|
||||
index = ~index;
|
||||
|
||||
// BinarySearch will return the index of the first element _greater_ than the search
|
||||
// This is the inactive point - the active point is the one before it (index - 1)
|
||||
return list[index - 1];
|
||||
}
|
||||
}
|
||||
}
|
13
osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
Normal file
13
osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// 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.Beatmaps.ControlPoints
|
||||
{
|
||||
public class DifficultyControlPoint : ControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The speed multiplier at this control point.
|
||||
/// </summary>
|
||||
public double SpeedMultiplier = 1;
|
||||
}
|
||||
}
|
18
osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
Normal file
18
osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class EffectControlPoint : ControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this control point enables Kiai mode.
|
||||
/// </summary>
|
||||
public bool KiaiMode;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the first bar line of this control point is ignored.
|
||||
/// </summary>
|
||||
public bool OmitFirstBarLine;
|
||||
}
|
||||
}
|
18
osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
Normal file
18
osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class SoundControlPoint : ControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The default sample bank at this control point.
|
||||
/// </summary>
|
||||
public string SampleBank;
|
||||
|
||||
/// <summary>
|
||||
/// The default sample volume at this control point.
|
||||
/// </summary>
|
||||
public int SampleVolume;
|
||||
}
|
||||
}
|
20
osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
Normal file
20
osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class TimingControlPoint : ControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time signature at this control point.
|
||||
/// </summary>
|
||||
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
|
||||
|
||||
/// <summary>
|
||||
/// The beat length at this control point.
|
||||
/// </summary>
|
||||
public double BeatLength = 500;
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps
|
||||
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
|
||||
|
||||
foreach (var h in Objects)
|
||||
h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);
|
||||
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
|
||||
|
||||
PreprocessHitObjects();
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
internal class DifficultyColouredContainer : Container, IHasAccentColour
|
||||
public class DifficultyColouredContainer : Container, IHasAccentColour
|
||||
{
|
||||
public Color4 AccentColour { get; set; }
|
||||
|
||||
|
@ -11,7 +11,7 @@ using OpenTK.Graphics;
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
|
||||
internal class DifficultyIcon : DifficultyColouredContainer
|
||||
public class DifficultyIcon : DifficultyColouredContainer
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
|
||||
|
@ -1,13 +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
|
||||
|
||||
namespace osu.Game.Beatmaps.Events
|
||||
{
|
||||
public class BackgroundEvent : Event
|
||||
{
|
||||
/// <summary>
|
||||
/// The file name.
|
||||
/// </summary>
|
||||
public string Filename;
|
||||
}
|
||||
}
|
@ -1,13 +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
|
||||
|
||||
namespace osu.Game.Beatmaps.Events
|
||||
{
|
||||
public abstract class Event
|
||||
{
|
||||
/// <summary>
|
||||
/// The event start time.
|
||||
/// </summary>
|
||||
public double StartTime;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Beatmaps.Events
|
||||
{
|
||||
public class EventInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// All the background events.
|
||||
/// </summary>
|
||||
public readonly List<BackgroundEvent> Backgrounds = new List<BackgroundEvent>();
|
||||
|
||||
/// <summary>
|
||||
/// All the break events.
|
||||
/// </summary>
|
||||
public readonly List<BreakEvent> Breaks = new List<BreakEvent>();
|
||||
|
||||
/// <summary>
|
||||
/// Total duration of all breaks.
|
||||
/// </summary>
|
||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the active background at a time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the background at.</param>
|
||||
/// <returns>The background.</returns>
|
||||
public BackgroundEvent BackgroundAt(double time) => Backgrounds.FirstOrDefault(b => b.StartTime <= time);
|
||||
}
|
||||
}
|
@ -5,10 +5,10 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Beatmaps.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -217,18 +217,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
case EventType.Background:
|
||||
string filename = split[2].Trim('"');
|
||||
|
||||
beatmap.EventInfo.Backgrounds.Add(new BackgroundEvent
|
||||
{
|
||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||
Filename = filename
|
||||
});
|
||||
|
||||
if (type == EventType.Background)
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
|
||||
break;
|
||||
case EventType.Break:
|
||||
var breakEvent = new BreakEvent
|
||||
var breakEvent = new BreakPeriod
|
||||
{
|
||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
||||
@ -237,7 +231,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (!breakEvent.HasEffect)
|
||||
return;
|
||||
|
||||
beatmap.EventInfo.Breaks.Add(breakEvent);
|
||||
beatmap.Breaks.Add(breakEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -248,6 +242,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
|
||||
|
||||
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
||||
if (split.Length >= 3)
|
||||
@ -282,18 +277,48 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (stringSampleSet == @"none")
|
||||
stringSampleSet = @"normal";
|
||||
|
||||
beatmap.TimingInfo.ControlPoints.Add(new ControlPoint
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
|
||||
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
|
||||
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
Time = time,
|
||||
BeatLength = beatLength,
|
||||
SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1,
|
||||
TimingChange = timingChange,
|
||||
TimeSignature = timeSignature,
|
||||
SampleBank = stringSampleSet,
|
||||
SampleVolume = sampleVolume,
|
||||
KiaiMode = kiaiMode,
|
||||
OmitFirstBarLine = omitFirstBarSignature
|
||||
});
|
||||
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
|
||||
{
|
||||
Time = time,
|
||||
BeatLength = beatLength,
|
||||
TimeSignature = timeSignature
|
||||
});
|
||||
}
|
||||
|
||||
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
|
||||
{
|
||||
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SpeedMultiplier = speedMultiplier
|
||||
});
|
||||
}
|
||||
|
||||
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
|
||||
{
|
||||
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SampleBank = stringSampleSet,
|
||||
SampleVolume = sampleVolume
|
||||
});
|
||||
}
|
||||
|
||||
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
|
||||
{
|
||||
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
|
||||
{
|
||||
Time = time,
|
||||
KiaiMode = kiaiMode,
|
||||
OmitFirstBarLine = omitFirstBarSignature
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours)
|
||||
|
@ -63,5 +63,7 @@ namespace osu.Game.Beatmaps.IO
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Stream GetUnderlyingStream();
|
||||
}
|
||||
}
|
@ -49,5 +49,7 @@ namespace osu.Game.Beatmaps.IO
|
||||
archive.Dispose();
|
||||
archiveStream.Dispose();
|
||||
}
|
||||
|
||||
public override Stream GetUnderlyingStream() => archiveStream;
|
||||
}
|
||||
}
|
@ -1,27 +1,32 @@
|
||||
// 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.Beatmaps.Events
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class BreakEvent : Event
|
||||
public class BreakPeriod
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum duration required for a break to have any effect.
|
||||
/// </summary>
|
||||
private const double min_break_duration = 650;
|
||||
|
||||
/// <summary>
|
||||
/// The break start time.
|
||||
/// </summary>
|
||||
public double StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The break end time.
|
||||
/// </summary>
|
||||
public double EndTime;
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the break.
|
||||
/// The break duration.
|
||||
/// </summary>
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the break has any effect. Breaks that are too short are culled before they reach the EventInfo.
|
||||
/// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
|
||||
/// </summary>
|
||||
public bool HasEffect => Duration >= min_break_duration;
|
||||
}
|
@ -1,20 +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
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class ControlPoint
|
||||
{
|
||||
public string SampleBank;
|
||||
public int SampleVolume;
|
||||
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
|
||||
public double Time;
|
||||
public double BeatLength = 500;
|
||||
public double SpeedMultiplier = 1;
|
||||
public bool TimingChange = true;
|
||||
public bool KiaiMode;
|
||||
public bool OmitFirstBarLine;
|
||||
|
||||
public ControlPoint Clone() => (ControlPoint)MemberwiseClone();
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class TimingInfo
|
||||
{
|
||||
public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
|
||||
|
||||
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
|
||||
public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
|
||||
public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time);
|
||||
|
||||
public double BPMAt(double time)
|
||||
{
|
||||
return 60000 / BeatLengthAt(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the speed multiplier at a time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the speed multiplier at.</param>
|
||||
/// <returns>The speed multiplier.</returns>
|
||||
public double SpeedMultiplierAt(double time)
|
||||
{
|
||||
ControlPoint overridePoint;
|
||||
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
|
||||
|
||||
return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the beat length at a time. This is expressed in milliseconds.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the beat length at.</param>
|
||||
/// <returns>The beat length.</returns>
|
||||
public double BeatLengthAt(double time)
|
||||
{
|
||||
ControlPoint overridePoint;
|
||||
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
|
||||
|
||||
return timingPoint.BeatLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the timing point at a time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the timing point at.</param>
|
||||
/// <param name="overridePoint">The timing point containing the velocity change of the returned timing point.</param>
|
||||
/// <returns>The timing point.</returns>
|
||||
public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
|
||||
{
|
||||
overridePoint = null;
|
||||
|
||||
ControlPoint timingPoint = null;
|
||||
foreach (var controlPoint in ControlPoints)
|
||||
{
|
||||
// Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s).
|
||||
// This null check makes it so that the first ControlPoint that makes a timing change is used as
|
||||
// the timingPoint for those HitObject(s).
|
||||
if (controlPoint.Time <= time || timingPoint == null)
|
||||
{
|
||||
if (controlPoint.TimingChange)
|
||||
{
|
||||
timingPoint = controlPoint;
|
||||
overridePoint = null;
|
||||
}
|
||||
else
|
||||
overridePoint = controlPoint;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
return timingPoint ?? new ControlPoint();
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.IO;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Screens.Menu;
|
||||
using SQLite.Net;
|
||||
using SQLiteNetExtensions.Extensions;
|
||||
|
||||
@ -38,6 +39,10 @@ namespace osu.Game.Database
|
||||
{
|
||||
foreach (var b in GetAllWithChildren<BeatmapSetInfo>(b => b.DeletePending))
|
||||
{
|
||||
if (b.Hash == Intro.MENU_MUSIC_BEATMAP_HASH)
|
||||
// this is a bit hacky, but will do for now.
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
Storage.Delete(b.Path);
|
||||
@ -97,50 +102,49 @@ namespace osu.Game.Database
|
||||
typeof(BeatmapDifficulty),
|
||||
};
|
||||
|
||||
public void Import(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Import(ArchiveReader.GetReader(Storage, path));
|
||||
|
||||
// We may or may not want to delete the file depending on where it is stored.
|
||||
// e.g. reconstructing/repairing database with beatmaps from default storage.
|
||||
// Also, not always a single file, i.e. for LegacyFilesystemReader
|
||||
// TODO: Add a check to prevent files from storage to be deleted.
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $@"Could not delete file at {path}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e = e.InnerException ?? e;
|
||||
Logger.Error(e, @"Could not import beatmap set");
|
||||
}
|
||||
}
|
||||
|
||||
public void Import(ArchiveReader archiveReader)
|
||||
{
|
||||
BeatmapSetInfo set = getBeatmapSet(archiveReader);
|
||||
|
||||
//If we have an ID then we already exist in the database.
|
||||
if (set.ID == 0)
|
||||
Import(new[] { set });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import multiple <see cref="BeatmapSetInfo"/> from <paramref name="paths"/>.
|
||||
/// </summary>
|
||||
/// <param name="paths">Multiple locations on disk</param>
|
||||
public void Import(IEnumerable<string> paths)
|
||||
public void Import(params string[] paths)
|
||||
{
|
||||
foreach (string p in paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeatmapSetInfo set = getBeatmapSet(p);
|
||||
|
||||
//If we have an ID then we already exist in the database.
|
||||
if (set.ID == 0)
|
||||
Import(new[] { set });
|
||||
|
||||
// We may or may not want to delete the file depending on where it is stored.
|
||||
// e.g. reconstructing/repairing database with beatmaps from default storage.
|
||||
// Also, not always a single file, i.e. for LegacyFilesystemReader
|
||||
// TODO: Add a check to prevent files from storage to be deleted.
|
||||
try
|
||||
{
|
||||
File.Delete(p);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $@"Could not delete file at {p}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e = e.InnerException ?? e;
|
||||
Logger.Error(e, @"Could not import beatmap set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import <see cref="BeatmapSetInfo"/> from <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">Location on disk</param>
|
||||
public void Import(string path)
|
||||
{
|
||||
Import(new[] { path });
|
||||
Import(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -148,29 +152,26 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <param name="path">Content location</param>
|
||||
/// <returns><see cref="BeatmapSetInfo"/></returns>
|
||||
private BeatmapSetInfo getBeatmapSet(string path)
|
||||
{
|
||||
string hash = null;
|
||||
private BeatmapSetInfo getBeatmapSet(string path) => getBeatmapSet(ArchiveReader.GetReader(Storage, path));
|
||||
|
||||
private BeatmapSetInfo getBeatmapSet(ArchiveReader archiveReader)
|
||||
{
|
||||
BeatmapMetadata metadata;
|
||||
|
||||
using (var reader = ArchiveReader.GetReader(Storage, path))
|
||||
{
|
||||
using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0])))
|
||||
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
|
||||
}
|
||||
using (var stream = new StreamReader(archiveReader.GetStream(archiveReader.BeatmapFilenames[0])))
|
||||
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
|
||||
|
||||
if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
|
||||
string hash;
|
||||
string path;
|
||||
|
||||
using (var input = archiveReader.GetUnderlyingStream())
|
||||
{
|
||||
using (var input = Storage.GetStream(path))
|
||||
{
|
||||
hash = input.GetMd5Hash();
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
|
||||
if (!Storage.Exists(path))
|
||||
using (var output = Storage.GetStream(path, FileAccess.Write))
|
||||
input.CopyTo(output);
|
||||
}
|
||||
hash = input.GetMd5Hash();
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
|
||||
if (!Storage.Exists(path))
|
||||
using (var output = Storage.GetStream(path, FileAccess.Write))
|
||||
input.CopyTo(output);
|
||||
}
|
||||
|
||||
var existing = Connection.Table<BeatmapSetInfo>().FirstOrDefault(b => b.Hash == hash);
|
||||
|
@ -43,6 +43,9 @@ namespace osu.Game.Database
|
||||
[Ignore]
|
||||
public BeatmapMetrics Metrics { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public BeatmapOnlineInfo OnlineInfo { get; set; }
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonProperty("file_md5")]
|
||||
|
38
osu.Game/Database/BeatmapOnlineInfo.cs
Normal file
38
osu.Game/Database/BeatmapOnlineInfo.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Beatmap info retrieved for previewing locally without having the beatmap downloaded.
|
||||
/// </summary>
|
||||
public class BeatmapOnlineInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The different sizes of cover art for this beatmap: cover, cover@2x, card, card@2x, list, list@2x.
|
||||
/// </summary>
|
||||
[JsonProperty(@"covers")]
|
||||
public IEnumerable<string> Covers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A small sample clip of this beatmap's song.
|
||||
/// </summary>
|
||||
[JsonProperty(@"previewUrl")]
|
||||
public string Preview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of plays this beatmap has.
|
||||
/// </summary>
|
||||
[JsonProperty(@"play_count")]
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of people who have favourited this map.
|
||||
/// </summary>
|
||||
[JsonProperty(@"favourite_count")]
|
||||
public int FavouriteCount { get; set; }
|
||||
}
|
||||
}
|
37
osu.Game/Database/OnlineWorkingBeatmap.cs
Normal file
37
osu.Game/Database/OnlineWorkingBeatmap.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
internal class OnlineWorkingBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly TextureStore textures;
|
||||
private readonly TrackManager tracks;
|
||||
|
||||
public OnlineWorkingBeatmap(BeatmapInfo beatmapInfo, TextureStore textures, TrackManager tracks) : base(beatmapInfo)
|
||||
{
|
||||
this.textures = textures;
|
||||
this.tracks = tracks;
|
||||
}
|
||||
|
||||
protected override Beatmap GetBeatmap()
|
||||
{
|
||||
return new Beatmap();
|
||||
}
|
||||
|
||||
protected override Texture GetBackground()
|
||||
{
|
||||
return textures.Get(BeatmapInfo.OnlineInfo.Covers.FirstOrDefault());
|
||||
}
|
||||
|
||||
protected override Track GetTrack()
|
||||
{
|
||||
return tracks.Get(BeatmapInfo.OnlineInfo.Preview);
|
||||
}
|
||||
}
|
||||
}
|
23
osu.Game/Database/RankStatus.cs
Normal file
23
osu.Game/Database/RankStatus.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public enum RankStatus
|
||||
{
|
||||
Any = 7,
|
||||
[Description("Ranked & Approved")]
|
||||
RankedApproved = 0,
|
||||
Approved = 1,
|
||||
Loved = 8,
|
||||
Favourites = 2,
|
||||
[Description("Mod Requests")]
|
||||
ModRequests = 3,
|
||||
Pending = 4,
|
||||
Graveyard = 5,
|
||||
[Description("My Maps")]
|
||||
MyMaps = 6,
|
||||
}
|
||||
}
|
@ -1,20 +1,29 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.MathUtils;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using System;
|
||||
using osu.Framework.Graphics.OpenGL;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using OpenTK.Graphics.ES30;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Allocation;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Batches;
|
||||
using osu.Framework.Lists;
|
||||
|
||||
namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
public class Triangles : Container<Triangle>
|
||||
public class Triangles : Drawable
|
||||
{
|
||||
private const float triangle_size = 100;
|
||||
|
||||
public override bool HandleInput => false;
|
||||
|
||||
public Color4 ColourLight = Color4.White;
|
||||
@ -44,6 +53,33 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
/// </summary>
|
||||
public bool HideAlphaDiscrepancies = true;
|
||||
|
||||
/// <summary>
|
||||
/// The relative velocity of the triangles. Default is 1.
|
||||
/// </summary>
|
||||
public float Velocity = 1;
|
||||
|
||||
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
|
||||
|
||||
private Shader shader;
|
||||
private readonly Texture texture;
|
||||
|
||||
public Triangles()
|
||||
{
|
||||
texture = Texture.WhitePixel;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders)
|
||||
{
|
||||
shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
addTriangles(true);
|
||||
}
|
||||
|
||||
public float TriangleScale
|
||||
{
|
||||
get { return triangleScale; }
|
||||
@ -52,42 +88,71 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
float change = value / triangleScale;
|
||||
triangleScale = value;
|
||||
|
||||
if (change != 1)
|
||||
Children.ForEach(t => t.Scale *= change);
|
||||
for (int i = 0; i < parts.Count; i++)
|
||||
{
|
||||
TriangleParticle newParticle = parts[i];
|
||||
newParticle.Scale *= change;
|
||||
parts[i] = newParticle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
addTriangles(true);
|
||||
}
|
||||
|
||||
private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
float adjustedAlpha = HideAlphaDiscrepancies ?
|
||||
// Cubically scale alpha to make it drop off more sharply.
|
||||
(float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
|
||||
1;
|
||||
Invalidate(Invalidation.DrawNode, shallPropagate: false);
|
||||
|
||||
foreach (var t in Children)
|
||||
for (int i = 0; i < parts.Count; i++)
|
||||
{
|
||||
t.Alpha = adjustedAlpha;
|
||||
t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale);
|
||||
if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0)
|
||||
t.Expire();
|
||||
TriangleParticle newParticle = parts[i];
|
||||
|
||||
float adjustedAlpha = HideAlphaDiscrepancies ?
|
||||
// Cubically scale alpha to make it drop off more sharply.
|
||||
(float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
|
||||
1;
|
||||
|
||||
|
||||
newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
|
||||
newParticle.Colour.A = adjustedAlpha;
|
||||
|
||||
parts[i] = newParticle;
|
||||
|
||||
if (!CreateNewTriangles)
|
||||
continue;
|
||||
|
||||
float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
|
||||
|
||||
if (bottomPos < 0)
|
||||
parts.RemoveAt(i);
|
||||
}
|
||||
|
||||
if (CreateNewTriangles)
|
||||
addTriangles(false);
|
||||
addTriangles(false);
|
||||
}
|
||||
|
||||
protected virtual Triangle CreateTriangle()
|
||||
private void addTriangles(bool randomY)
|
||||
{
|
||||
int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
|
||||
|
||||
for (int i = 0; i < aimTriangleCount - parts.Count; i++)
|
||||
parts.Add(createTriangle(randomY));
|
||||
}
|
||||
|
||||
private TriangleParticle createTriangle(bool randomY)
|
||||
{
|
||||
TriangleParticle particle = CreateTriangle();
|
||||
|
||||
particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
|
||||
particle.Colour = CreateTriangleShade();
|
||||
|
||||
return particle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a triangle particle with a random scale.
|
||||
/// </summary>
|
||||
/// <returns>The triangle particle.</returns>
|
||||
protected virtual TriangleParticle CreateTriangle()
|
||||
{
|
||||
const float std_dev = 0.16f;
|
||||
const float mean = 0.5f;
|
||||
@ -97,32 +162,100 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1)
|
||||
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2)
|
||||
|
||||
const float size = 100;
|
||||
|
||||
return new EquilateralTriangle
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Size = new Vector2(size),
|
||||
Scale = new Vector2(scale),
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
Colour = GetTriangleShade(),
|
||||
Depth = scale,
|
||||
};
|
||||
return new TriangleParticle { Scale = scale };
|
||||
}
|
||||
|
||||
protected virtual Color4 GetTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
|
||||
/// <summary>
|
||||
/// Creates a shade of colour for the triangles.
|
||||
/// </summary>
|
||||
/// <returns>The colour.</returns>
|
||||
protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
|
||||
|
||||
private void addTriangles(bool randomY)
|
||||
protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
|
||||
|
||||
private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
|
||||
protected override void ApplyDrawNode(DrawNode node)
|
||||
{
|
||||
int addCount = aimTriangleCount - Children.Count();
|
||||
for (int i = 0; i < addCount; i++)
|
||||
base.ApplyDrawNode(node);
|
||||
|
||||
var trianglesNode = (TrianglesDrawNode)node;
|
||||
|
||||
trianglesNode.Shader = shader;
|
||||
trianglesNode.Texture = texture;
|
||||
trianglesNode.Size = DrawSize;
|
||||
trianglesNode.Shared = sharedData;
|
||||
|
||||
trianglesNode.Parts.Clear();
|
||||
trianglesNode.Parts.AddRange(parts);
|
||||
}
|
||||
|
||||
private class TrianglesDrawNodeSharedData
|
||||
{
|
||||
public readonly LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
|
||||
}
|
||||
|
||||
private class TrianglesDrawNode : DrawNode
|
||||
{
|
||||
public Shader Shader;
|
||||
public Texture Texture;
|
||||
|
||||
public TrianglesDrawNodeSharedData Shared;
|
||||
|
||||
public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
|
||||
public Vector2 Size;
|
||||
|
||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||
{
|
||||
var sprite = CreateTriangle();
|
||||
float triangleHeight = sprite.DrawHeight / DrawHeight;
|
||||
sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1);
|
||||
Add(sprite);
|
||||
base.Draw(vertexAction);
|
||||
|
||||
Shader.Bind();
|
||||
Texture.TextureGL.Bind();
|
||||
|
||||
foreach (TriangleParticle particle in Parts)
|
||||
{
|
||||
var offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
|
||||
|
||||
var triangle = new Triangle(
|
||||
particle.Position * Size * DrawInfo.Matrix,
|
||||
(particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix,
|
||||
(particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix
|
||||
);
|
||||
|
||||
ColourInfo colourInfo = DrawInfo.Colour;
|
||||
colourInfo.ApplyChild(particle.Colour);
|
||||
|
||||
Texture.DrawTriangle(triangle, colourInfo, null, Shared.VertexBatch.Add);
|
||||
}
|
||||
|
||||
Shader.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
protected struct TriangleParticle : IComparable<TriangleParticle>
|
||||
{
|
||||
/// <summary>
|
||||
/// The position of the top vertex of the triangle.
|
||||
/// </summary>
|
||||
public Vector2 Position;
|
||||
|
||||
/// <summary>
|
||||
/// The colour of the triangle.
|
||||
/// </summary>
|
||||
public Color4 Colour;
|
||||
|
||||
/// <summary>
|
||||
/// The scale of the triangle.
|
||||
/// </summary>
|
||||
public float Scale;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref="TriangleParticle"/>s. This is a reverse comparer because when the
|
||||
/// triangles are added to the particles list, they should be drawn from largest to smallest
|
||||
/// such that the smaller triangles appear on top.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
osu.Game/Graphics/Containers/BeatSyncedContainer.cs
Normal file
67
osu.Game/Graphics/Containers/BeatSyncedContainer.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
public class BeatSyncedContainer : Container
|
||||
{
|
||||
protected readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
private int lastBeat;
|
||||
private TimingControlPoint lastTimingPoint;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time before a beat we should fire <see cref="OnNewBeat(int, TimingControlPoint, EffectControlPoint, TrackAmplitudes)"/>.
|
||||
/// This allows for adding easing to animations that may be synchronised to the beat.
|
||||
/// </summary>
|
||||
protected double EarlyActivationMilliseconds;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (Beatmap.Value?.Track == null)
|
||||
return;
|
||||
|
||||
double currentTrackTime = Beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
|
||||
|
||||
TimingControlPoint timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
|
||||
EffectControlPoint effectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
|
||||
|
||||
if (timingPoint.BeatLength == 0)
|
||||
return;
|
||||
|
||||
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
|
||||
|
||||
// The beats before the start of the first control point are off by 1, this should do the trick
|
||||
if (currentTrackTime < timingPoint.Time)
|
||||
beatIndex--;
|
||||
|
||||
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||
return;
|
||||
|
||||
double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
|
||||
|
||||
using (BeginDelayedSequence(offsetFromBeat, true))
|
||||
OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes);
|
||||
|
||||
lastBeat = beatIndex;
|
||||
lastTimingPoint = timingPoint;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game)
|
||||
{
|
||||
Beatmap.BindTo(game.Beatmap);
|
||||
}
|
||||
|
||||
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
|
||||
{
|
||||
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
|
||||
protected override IEnumerable<T> FlowingChildren => base.FlowingChildren.Reverse();
|
||||
}
|
||||
}
|
171
osu.Game/Graphics/Containers/SectionsContainer.cs
Normal file
171
osu.Game/Graphics/Containers/SectionsContainer.cs
Normal file
@ -0,0 +1,171 @@
|
||||
// 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.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that can scroll to each section inside it.
|
||||
/// </summary>
|
||||
public class SectionsContainer : Container
|
||||
{
|
||||
private Drawable expandableHeader, fixedHeader, footer;
|
||||
public readonly ScrollContainer ScrollContainer;
|
||||
private readonly Container<Drawable> sectionsContainer;
|
||||
|
||||
public Drawable ExpandableHeader
|
||||
{
|
||||
get { return expandableHeader; }
|
||||
set
|
||||
{
|
||||
if (value == expandableHeader) return;
|
||||
|
||||
if (expandableHeader != null)
|
||||
Remove(expandableHeader);
|
||||
expandableHeader = value;
|
||||
if (value == null) return;
|
||||
|
||||
Add(expandableHeader);
|
||||
lastKnownScroll = float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable FixedHeader
|
||||
{
|
||||
get { return fixedHeader; }
|
||||
set
|
||||
{
|
||||
if (value == fixedHeader) return;
|
||||
|
||||
if (fixedHeader != null)
|
||||
Remove(fixedHeader);
|
||||
fixedHeader = value;
|
||||
if (value == null) return;
|
||||
|
||||
Add(fixedHeader);
|
||||
lastKnownScroll = float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable Footer
|
||||
{
|
||||
get { return footer; }
|
||||
set
|
||||
{
|
||||
if (value == footer) return;
|
||||
|
||||
if (footer != null)
|
||||
ScrollContainer.Remove(footer);
|
||||
footer = value;
|
||||
if (value == null) return;
|
||||
|
||||
footer.Anchor |= Anchor.y2;
|
||||
footer.Origin |= Anchor.y2;
|
||||
ScrollContainer.Add(footer);
|
||||
lastKnownScroll = float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public Bindable<Drawable> SelectedSection { get; } = new Bindable<Drawable>();
|
||||
|
||||
protected virtual Container<Drawable> CreateScrollContentContainer()
|
||||
=> new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
private List<Drawable> sections = new List<Drawable>();
|
||||
public IEnumerable<Drawable> Sections
|
||||
{
|
||||
get { return sections; }
|
||||
set
|
||||
{
|
||||
foreach (var section in sections)
|
||||
sectionsContainer.Remove(section);
|
||||
|
||||
sections = value.ToList();
|
||||
if (sections.Count == 0) return;
|
||||
|
||||
sectionsContainer.Add(sections);
|
||||
SelectedSection.Value = sections[0];
|
||||
lastKnownScroll = float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
private float headerHeight, footerHeight;
|
||||
private readonly MarginPadding originalSectionsMargin;
|
||||
private void updateSectionsMargin()
|
||||
{
|
||||
if (sections.Count == 0) return;
|
||||
|
||||
var newMargin = originalSectionsMargin;
|
||||
newMargin.Top += headerHeight;
|
||||
newMargin.Bottom += footerHeight;
|
||||
|
||||
sectionsContainer.Margin = newMargin;
|
||||
}
|
||||
|
||||
public SectionsContainer()
|
||||
{
|
||||
Add(ScrollContainer = new ScrollContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = false,
|
||||
Children = new Drawable[] { sectionsContainer = CreateScrollContentContainer() }
|
||||
});
|
||||
originalSectionsMargin = sectionsContainer.Margin;
|
||||
}
|
||||
|
||||
private float lastKnownScroll;
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0);
|
||||
float footerH = Footer?.LayoutSize.Y ?? 0;
|
||||
if (headerH != headerHeight || footerH != footerHeight)
|
||||
{
|
||||
headerHeight = headerH;
|
||||
footerHeight = footerH;
|
||||
updateSectionsMargin();
|
||||
}
|
||||
|
||||
float currentScroll = Math.Max(0, ScrollContainer.Current);
|
||||
if (currentScroll != lastKnownScroll)
|
||||
{
|
||||
lastKnownScroll = currentScroll;
|
||||
|
||||
if (expandableHeader != null && fixedHeader != null)
|
||||
{
|
||||
float offset = Math.Min(expandableHeader.LayoutSize.Y, currentScroll);
|
||||
|
||||
expandableHeader.Y = -offset;
|
||||
fixedHeader.Y = -offset + expandableHeader.LayoutSize.Y;
|
||||
}
|
||||
|
||||
Drawable bestMatch = null;
|
||||
float minDiff = float.MaxValue;
|
||||
|
||||
foreach (var section in sections)
|
||||
{
|
||||
float diff = Math.Abs(ScrollContainer.GetChildPosInContent(section) - currentScroll);
|
||||
if (diff < minDiff)
|
||||
{
|
||||
minDiff = diff;
|
||||
bestMatch = section;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch != null)
|
||||
SelectedSection.Value = bestMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -87,8 +87,5 @@ namespace osu.Game.Graphics
|
||||
public readonly Color4 RedDarker = FromHex(@"870000");
|
||||
|
||||
public readonly Color4 ChatBlue = FromHex(@"17292e");
|
||||
|
||||
public readonly Color4 SpinnerBase = FromHex(@"002c3c");
|
||||
public readonly Color4 SpinnerFill = FromHex(@"005b7c");
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour };
|
||||
|
||||
private class OsuDropdownMenuItem : DropdownMenuItem<T>
|
||||
public class OsuDropdownMenuItem : DropdownMenuItem<T>
|
||||
{
|
||||
public OsuDropdownMenuItem(string text, T current) : base(text, current)
|
||||
{
|
||||
@ -60,7 +60,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
chevron = new TextAwesome
|
||||
Chevron = new TextAwesome
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Icon = FontAwesome.fa_chevron_right,
|
||||
@ -84,12 +84,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private Color4? accentColour;
|
||||
|
||||
private readonly TextAwesome chevron;
|
||||
protected readonly TextAwesome Chevron;
|
||||
|
||||
protected override void FormatForeground(bool hover = false)
|
||||
{
|
||||
base.FormatForeground(hover);
|
||||
chevron.Alpha = hover ? 1 : 0;
|
||||
Chevron.Alpha = hover ? 1 : 0;
|
||||
}
|
||||
|
||||
public Color4 AccentColour
|
||||
@ -113,13 +113,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected class OsuDropdownHeader : DropdownHeader
|
||||
public class OsuDropdownHeader : DropdownHeader
|
||||
{
|
||||
private readonly SpriteText label;
|
||||
protected readonly SpriteText Text;
|
||||
protected override string Label
|
||||
{
|
||||
get { return label.Text; }
|
||||
set { label.Text = value; }
|
||||
get { return Text.Text; }
|
||||
set { Text.Text = value; }
|
||||
}
|
||||
|
||||
protected readonly TextAwesome Icon;
|
||||
@ -146,7 +146,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
label = new OsuSpriteText
|
||||
Text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
32
osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
Normal file
32
osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuEnumDropdown<T> : OsuDropdown<T>
|
||||
{
|
||||
public OsuEnumDropdown()
|
||||
{
|
||||
if (!typeof(T).IsEnum)
|
||||
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
|
||||
|
||||
List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
|
||||
foreach(var val in (T[])Enum.GetValues(typeof(T)))
|
||||
{
|
||||
var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
|
||||
items.Add(
|
||||
new KeyValuePair<string, T>(
|
||||
field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
|
||||
val
|
||||
)
|
||||
);
|
||||
}
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
}
|
@ -57,9 +57,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private class OsuTabItem : TabItem<T>
|
||||
public class OsuTabItem : TabItem<T>
|
||||
{
|
||||
private readonly SpriteText text;
|
||||
protected readonly SpriteText Text;
|
||||
private readonly Box box;
|
||||
|
||||
private Color4? accentColour;
|
||||
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
accentColour = value;
|
||||
if (!Active)
|
||||
text.Colour = value;
|
||||
Text.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,13 +94,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private void fadeActive()
|
||||
{
|
||||
box.FadeIn(transition_length, EasingTypes.OutQuint);
|
||||
text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
|
||||
Text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
|
||||
}
|
||||
|
||||
private void fadeInactive()
|
||||
{
|
||||
box.FadeOut(transition_length, EasingTypes.OutQuint);
|
||||
text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
|
||||
Text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
@ -130,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText
|
||||
Text = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
||||
Origin = Anchor.BottomLeft,
|
||||
|
@ -288,7 +288,7 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
APIRequest req;
|
||||
while (oldQueue.TryDequeue(out req))
|
||||
req.Fail(new Exception(@"Disconnected from server"));
|
||||
req.Fail(new WebException(@"Disconnected from server"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
@ -20,10 +21,7 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
string channelString = string.Empty;
|
||||
foreach (Channel c in channels)
|
||||
channelString += c.Id + ",";
|
||||
channelString = channelString.TrimEnd(',');
|
||||
string channelString = string.Join(",", channels.Select(x => x.Id));
|
||||
|
||||
var req = base.CreateWebRequest();
|
||||
req.AddParameter(@"channels", channelString);
|
||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public Bindable<bool> Joined = new Bindable<bool>();
|
||||
|
||||
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
|
||||
|
||||
public readonly SortedList<Message> Messages = new SortedList<Message>((m1, m2) => m1.Id.CompareTo(m2.Id));
|
||||
|
||||
public bool ReadOnly => Name != "#lazer";
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class Message
|
||||
public class Message : IComparable<Message>, IEquatable<Message>
|
||||
{
|
||||
[JsonProperty(@"message_id")]
|
||||
public readonly long Id;
|
||||
@ -42,17 +42,11 @@ namespace osu.Game.Online.Chat
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var objMessage = obj as Message;
|
||||
public int CompareTo(Message other) => Id.CompareTo(other.Id);
|
||||
|
||||
return Id == objMessage?.Id;
|
||||
}
|
||||
public bool Equals(Message other) => Id == other?.Id;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
|
||||
public enum TargetType
|
||||
|
17
osu.Game/Online/Multiplayer/Room.cs
Normal file
17
osu.Game/Online/Multiplayer/Room.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public class Room
|
||||
{
|
||||
public Bindable<string> Name = new Bindable<string>();
|
||||
public Bindable<User> Host = new Bindable<User>();
|
||||
public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
|
||||
public Bindable<BeatmapMetadata> Beatmap = new Bindable<BeatmapMetadata>();
|
||||
}
|
||||
}
|
26
osu.Game/Online/Multiplayer/RoomStatus.cs
Normal file
26
osu.Game/Online/Multiplayer/RoomStatus.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public abstract class RoomStatus
|
||||
{
|
||||
public abstract string Message { get; }
|
||||
public abstract Color4 GetAppropriateColour(OsuColour colours);
|
||||
}
|
||||
|
||||
public class RoomStatusOpen : RoomStatus
|
||||
{
|
||||
public override string Message => @"Welcoming Players";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
||||
}
|
||||
|
||||
public class RoomStatusPlaying : RoomStatus
|
||||
{
|
||||
public override string Message => @"Now Playing";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
||||
}
|
||||
}
|
@ -41,6 +41,8 @@ namespace osu.Game
|
||||
|
||||
private DialogOverlay dialogOverlay;
|
||||
|
||||
private DirectOverlay direct;
|
||||
|
||||
private Intro intro
|
||||
{
|
||||
get
|
||||
@ -70,6 +72,8 @@ namespace osu.Game
|
||||
|
||||
public void ToggleSettings() => settings.ToggleVisibility();
|
||||
|
||||
public void ToggleDirect() => direct.ToggleVisibility();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -82,7 +86,7 @@ namespace osu.Game
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
var paths = args.Where(a => !a.StartsWith(@"-"));
|
||||
Task.Run(() => BeatmapDatabase.Import(paths));
|
||||
Task.Run(() => BeatmapDatabase.Import(paths.ToArray()));
|
||||
}
|
||||
|
||||
Dependencies.Cache(this);
|
||||
@ -160,6 +164,7 @@ namespace osu.Game
|
||||
});
|
||||
|
||||
//overlay elements
|
||||
LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
|
||||
LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
|
||||
LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
|
||||
LoadComponentAsync(musicController = new MusicController
|
||||
@ -249,6 +254,9 @@ namespace osu.Game
|
||||
case Key.O:
|
||||
settings.ToggleVisibility();
|
||||
return true;
|
||||
case Key.D:
|
||||
direct.ToggleVisibility();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +288,7 @@ namespace osu.Game
|
||||
Toolbar.State = Visibility.Hidden;
|
||||
musicController.State = Visibility.Hidden;
|
||||
chat.State = Visibility.Hidden;
|
||||
direct.State = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Chat
|
||||
public class DrawableChannel : Container
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
private readonly FillFlowContainer flow;
|
||||
private readonly FillFlowContainer<ChatLine> flow;
|
||||
private readonly ScrollContainer scroll;
|
||||
|
||||
public DrawableChannel(Channel channel)
|
||||
@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Chat
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
flow = new FillFlowContainer
|
||||
flow = new FillFlowContainer<ChatLine>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -63,19 +63,18 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
|
||||
|
||||
//up to last Channel.MAX_HISTORY messages
|
||||
flow.Add(displayMessages.Select(m => new ChatLine(m)));
|
||||
|
||||
if (scroll.IsScrolledToEnd(10) || !flow.Children.Any())
|
||||
scrollToEnd();
|
||||
|
||||
//up to last Channel.MAX_HISTORY messages
|
||||
foreach (Message m in displayMessages)
|
||||
{
|
||||
var d = new ChatLine(m);
|
||||
flow.Add(d);
|
||||
}
|
||||
var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
|
||||
int count = staleMessages.Length - Channel.MAX_HISTORY;
|
||||
|
||||
while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY)
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var d = flow.Children.First(c => c.LifetimeEnd == double.MaxValue);
|
||||
var d = staleMessages[i];
|
||||
if (!scroll.IsScrolledToEnd(10))
|
||||
scroll.OffsetScrollPosition(-d.DrawHeight);
|
||||
d.Expire();
|
||||
|
@ -239,6 +239,7 @@ namespace osu.Game.Overlays
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
loading.FadeOut(100);
|
||||
loading.Expire();
|
||||
|
||||
addChannel(channels.Find(c => c.Name == @"#lazer"));
|
||||
addChannel(channels.Find(c => c.Name == @"#osu"));
|
||||
@ -342,11 +343,8 @@ namespace osu.Game.Overlays
|
||||
fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
|
||||
fetchReq.Success += delegate (List<Message> messages)
|
||||
{
|
||||
var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct();
|
||||
|
||||
//batch messages per channel.
|
||||
foreach (var id in ids)
|
||||
careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id).ToArray());
|
||||
foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId))
|
||||
careChannels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||
|
||||
lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId;
|
||||
|
||||
|
193
osu.Game/Overlays/Direct/DirectGridPanel.cs
Normal file
193
osu.Game/Overlays/Direct/DirectGridPanel.cs
Normal file
@ -0,0 +1,193 @@
|
||||
// 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.Linq;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class DirectGridPanel : DirectPanel
|
||||
{
|
||||
private const float horizontal_padding = 10;
|
||||
private const float vertical_padding = 5;
|
||||
|
||||
private FillFlowContainer bottomPanel;
|
||||
|
||||
public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
|
||||
{
|
||||
Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
|
||||
CornerRadius = 4;
|
||||
Masking = true;
|
||||
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
Radius = 3f,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
FadeInFromZero(200, EasingTypes.Out);
|
||||
bottomPanel.LayoutDuration = 200;
|
||||
bottomPanel.LayoutEasing = EasingTypes.Out;
|
||||
bottomPanel.Origin = Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
GetBackground(textures),
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.5f),
|
||||
},
|
||||
bottomPanel = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, vertical_padding),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
|
||||
TextSize = 18,
|
||||
Font = @"Exo2.0-BoldItalic",
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = @"Exo2.0-BoldItalic",
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = vertical_padding,
|
||||
Bottom = vertical_padding,
|
||||
Left = horizontal_padding,
|
||||
Right = horizontal_padding,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "mapped by ",
|
||||
TextSize = 14,
|
||||
Shadow = false,
|
||||
Colour = colours.Gray5,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = SetInfo.Metadata.Author,
|
||||
TextSize = 14,
|
||||
Font = @"Exo2.0-SemiBoldItalic",
|
||||
Shadow = false,
|
||||
Colour = colours.BlueDark,
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = 14,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = $"from {SetInfo.Metadata.Source}",
|
||||
TextSize = 14,
|
||||
Shadow = false,
|
||||
Colour = colours.Gray5,
|
||||
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
|
||||
},
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = 20,
|
||||
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
|
||||
Children = GetDifficultyIcons(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
|
||||
Children = new[]
|
||||
{
|
||||
new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
|
||||
{
|
||||
Margin = new MarginPadding { Right = 1 },
|
||||
},
|
||||
new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
197
osu.Game/Overlays/Direct/DirectListPanel.cs
Normal file
197
osu.Game/Overlays/Direct/DirectListPanel.cs
Normal file
@ -0,0 +1,197 @@
|
||||
// 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 OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Database;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class DirectListPanel : DirectPanel
|
||||
{
|
||||
private const float horizontal_padding = 10;
|
||||
private const float vertical_padding = 5;
|
||||
private const float height = 70;
|
||||
|
||||
public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = height;
|
||||
CornerRadius = 5;
|
||||
Masking = true;
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
Radius = 3f,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
FadeInFromZero(200, EasingTypes.Out);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(LocalisationEngine localisation, TextureStore textures)
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
GetBackground(textures),
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
|
||||
TextSize = 18,
|
||||
Font = @"Exo2.0-BoldItalic",
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = @"Exo2.0-BoldItalic",
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = 20,
|
||||
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
|
||||
Children = GetDifficultyIcons(),
|
||||
},
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
|
||||
{
|
||||
Margin = new MarginPadding { Right = 1 },
|
||||
},
|
||||
new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "mapped by ",
|
||||
TextSize = 14,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = SetInfo.Metadata.Author,
|
||||
TextSize = 14,
|
||||
Font = @"Exo2.0-SemiBoldItalic",
|
||||
},
|
||||
},
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = $"from {SetInfo.Metadata.Source}",
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
TextSize = 14,
|
||||
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
|
||||
},
|
||||
},
|
||||
},
|
||||
new DownloadButton
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Size = new Vector2(height - vertical_padding * 2),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private class DownloadButton : ClickableContainer
|
||||
{
|
||||
private readonly TextAwesome icon;
|
||||
|
||||
public DownloadButton()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new TextAwesome
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
UseFullGlyphHeight = false,
|
||||
TextSize = 30,
|
||||
Icon = FontAwesome.fa_osu_chevron_down_o,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
osu.Game/Overlays/Direct/DirectPanel.cs
Normal file
88
osu.Game/Overlays/Direct/DirectPanel.cs
Normal file
@ -0,0 +1,88 @@
|
||||
// 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 OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public abstract class DirectPanel : Container
|
||||
{
|
||||
protected readonly BeatmapSetInfo SetInfo;
|
||||
|
||||
protected DirectPanel(BeatmapSetInfo setInfo)
|
||||
{
|
||||
SetInfo = setInfo;
|
||||
}
|
||||
|
||||
protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
|
||||
{
|
||||
var icons = new List<DifficultyIcon>();
|
||||
|
||||
foreach (var b in SetInfo.Beatmaps)
|
||||
icons.Add(new DifficultyIcon(b));
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
protected Drawable GetBackground(TextureStore textures)
|
||||
{
|
||||
return new AsyncLoadWrapper(new BeatmapBackgroundSprite(new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null))
|
||||
{
|
||||
FillMode = FillMode.Fill,
|
||||
OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out),
|
||||
}) { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
|
||||
public class Statistic : FillFlowContainer
|
||||
{
|
||||
private readonly SpriteText text;
|
||||
|
||||
private int value;
|
||||
public int Value
|
||||
{
|
||||
get { return value; }
|
||||
set
|
||||
{
|
||||
this.value = value;
|
||||
text.Text = Value.ToString(@"N0");
|
||||
}
|
||||
}
|
||||
|
||||
public Statistic(FontAwesome icon, int value = 0)
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Direction = FillDirection.Horizontal;
|
||||
Spacing = new Vector2(5f, 0f);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Font = @"Exo2.0-SemiBoldItalic",
|
||||
},
|
||||
new TextAwesome
|
||||
{
|
||||
Icon = icon,
|
||||
Shadow = true,
|
||||
TextSize = 14,
|
||||
Margin = new MarginPadding { Top = 1 },
|
||||
},
|
||||
};
|
||||
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
233
osu.Game/Overlays/Direct/FilterControl.cs
Normal file
233
osu.Game/Overlays/Direct/FilterControl.cs
Normal file
@ -0,0 +1,233 @@
|
||||
// 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 OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class FilterControl : Container
|
||||
{
|
||||
public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
|
||||
|
||||
private const float padding = 10;
|
||||
|
||||
private readonly Box tabStrip;
|
||||
private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
|
||||
|
||||
public readonly SearchTextBox Search;
|
||||
public readonly SortTabControl SortTabs;
|
||||
public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
|
||||
public readonly Bindable<DirectOverlay.PanelDisplayStyle> DisplayStyle = new Bindable<DirectOverlay.PanelDisplayStyle>();
|
||||
|
||||
protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
|
||||
|
||||
public FilterControl()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
DisplayStyle.Value = DirectOverlay.PanelDisplayStyle.Grid;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.FromHex(@"384552"),
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
tabStrip = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 1,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Search = new DirectSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = padding },
|
||||
},
|
||||
modeButtons = new FillFlowContainer<RulesetToggleButton>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(padding, 0f),
|
||||
Margin = new MarginPadding { Top = padding },
|
||||
},
|
||||
SortTabs = new SortTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding { Top = HEIGHT - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(5f, 0f),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new DisplayStyleToggleButton(FontAwesome.fa_th_large, DirectOverlay.PanelDisplayStyle.Grid, DisplayStyle),
|
||||
new DisplayStyleToggleButton(FontAwesome.fa_list_ul, DirectOverlay.PanelDisplayStyle.List, DisplayStyle),
|
||||
},
|
||||
},
|
||||
RankStatusDropdown = new SlimEnumDropdown<RankStatus>
|
||||
{
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 160f,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
|
||||
SortTabs.Current.Value = SortCriteria.Title;
|
||||
SortTabs.Current.TriggerChange();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
|
||||
{
|
||||
tabStrip.Colour = colours.Yellow;
|
||||
RankStatusDropdown.AccentColour = colours.BlueDark;
|
||||
|
||||
var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
|
||||
foreach (var r in rulesets.AllRulesets)
|
||||
{
|
||||
modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? b, r));
|
||||
}
|
||||
}
|
||||
|
||||
private class DirectSearchTextBox : SearchTextBox
|
||||
{
|
||||
protected override Color4 BackgroundUnfocused => backgroundColour;
|
||||
protected override Color4 BackgroundFocused => backgroundColour;
|
||||
|
||||
private Color4 backgroundColour;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
backgroundColour = colours.Gray2.Opacity(0.9f);
|
||||
}
|
||||
}
|
||||
|
||||
private class RulesetToggleButton : ClickableContainer
|
||||
{
|
||||
private readonly TextAwesome icon;
|
||||
|
||||
private RulesetInfo ruleset;
|
||||
public RulesetInfo Ruleset
|
||||
{
|
||||
get { return ruleset; }
|
||||
set
|
||||
{
|
||||
ruleset = value;
|
||||
icon.Icon = Ruleset.CreateInstance().Icon;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bindable<RulesetInfo> bindable;
|
||||
|
||||
private void Bindable_ValueChanged(RulesetInfo obj)
|
||||
{
|
||||
icon.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100);
|
||||
}
|
||||
|
||||
public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
|
||||
{
|
||||
this.bindable = bindable;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
icon = new TextAwesome
|
||||
{
|
||||
Origin = Anchor.TopLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
TextSize = 32,
|
||||
}
|
||||
};
|
||||
|
||||
Ruleset = ruleset;
|
||||
bindable.ValueChanged += Bindable_ValueChanged;
|
||||
Bindable_ValueChanged(bindable.Value);
|
||||
Action = () => bindable.Value = Ruleset;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (bindable != null)
|
||||
bindable.ValueChanged -= Bindable_ValueChanged;
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
||||
private class DisplayStyleToggleButton : ClickableContainer
|
||||
{
|
||||
private readonly TextAwesome icon;
|
||||
private readonly DirectOverlay.PanelDisplayStyle style;
|
||||
private readonly Bindable<DirectOverlay.PanelDisplayStyle> bindable;
|
||||
|
||||
public DisplayStyleToggleButton(FontAwesome icon, DirectOverlay.PanelDisplayStyle style, Bindable<DirectOverlay.PanelDisplayStyle> bindable)
|
||||
{
|
||||
this.bindable = bindable;
|
||||
this.style = style;
|
||||
Size = new Vector2(SlimEnumDropdown<DirectTab>.HEIGHT);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
this.icon = new TextAwesome
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = icon,
|
||||
TextSize = 18,
|
||||
UseFullGlyphHeight = false,
|
||||
Alpha = 0.5f,
|
||||
},
|
||||
};
|
||||
|
||||
bindable.ValueChanged += Bindable_ValueChanged;
|
||||
Bindable_ValueChanged(bindable.Value);
|
||||
Action = () => bindable.Value = this.style;
|
||||
}
|
||||
|
||||
private void Bindable_ValueChanged(DirectOverlay.PanelDisplayStyle style)
|
||||
{
|
||||
icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
bindable.ValueChanged -= Bindable_ValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
osu.Game/Overlays/Direct/Header.cs
Normal file
124
osu.Game/Overlays/Direct/Header.cs
Normal file
@ -0,0 +1,124 @@
|
||||
// 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.ComponentModel;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class Header : Container
|
||||
{
|
||||
public static readonly float HEIGHT = 90;
|
||||
|
||||
private readonly Box tabStrip;
|
||||
|
||||
public readonly OsuTabControl<DirectTab> Tabs;
|
||||
|
||||
public Header()
|
||||
{
|
||||
Height = HEIGHT;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.FromHex(@"252f3a"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Position = new Vector2(-35f, 5f),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TextAwesome
|
||||
{
|
||||
TextSize = 25,
|
||||
Icon = FontAwesome.fa_osu_chevron_down_o,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
TextSize = 25,
|
||||
Text = @"osu!direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
tabStrip = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
|
||||
Height = 1,
|
||||
},
|
||||
Tabs = new DirectTabControl
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Tabs.Current.Value = DirectTab.Search;
|
||||
Tabs.Current.TriggerChange();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
tabStrip.Colour = colours.Green;
|
||||
}
|
||||
|
||||
private class DirectTabControl : OsuTabControl<DirectTab>
|
||||
{
|
||||
protected override TabItem<DirectTab> CreateTabItem(DirectTab value) => new DirectTabItem(value);
|
||||
|
||||
public DirectTabControl()
|
||||
{
|
||||
Height = 25;
|
||||
AccentColour = Color4.White;
|
||||
}
|
||||
|
||||
private class DirectTabItem : OsuTabItem
|
||||
{
|
||||
public DirectTabItem(DirectTab value) : base(value)
|
||||
{
|
||||
Text.TextSize = 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DirectTab
|
||||
{
|
||||
Search,
|
||||
[Description("Newest Maps")]
|
||||
NewestMaps,
|
||||
[Description("Top Rated")]
|
||||
TopRated,
|
||||
[Description("Most Played")]
|
||||
MostPlayed
|
||||
}
|
||||
}
|
43
osu.Game/Overlays/Direct/SlimEnumDropdown.cs
Normal file
43
osu.Game/Overlays/Direct/SlimEnumDropdown.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
|
||||
{
|
||||
public const float HEIGHT = 25;
|
||||
|
||||
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
|
||||
protected override Menu CreateMenu() => new SlimMenu();
|
||||
|
||||
private class SlimDropdownHeader : OsuDropdownHeader
|
||||
{
|
||||
public SlimDropdownHeader()
|
||||
{
|
||||
Height = HEIGHT;
|
||||
Icon.TextSize = 16;
|
||||
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
BackgroundColour = Color4.Black.Opacity(0.25f);
|
||||
}
|
||||
}
|
||||
|
||||
private class SlimMenu : OsuMenu
|
||||
{
|
||||
public SlimMenu()
|
||||
{
|
||||
Background.Colour = Color4.Black.Opacity(0.25f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
osu.Game/Overlays/Direct/SortTabControl.cs
Normal file
117
osu.Game/Overlays/Direct/SortTabControl.cs
Normal file
@ -0,0 +1,117 @@
|
||||
// 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 OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class SortTabControl : OsuTabControl<SortCriteria>
|
||||
{
|
||||
protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new SortTabItem(value);
|
||||
|
||||
public SortTabControl()
|
||||
{
|
||||
Height = 30;
|
||||
}
|
||||
|
||||
private class SortTabItem : TabItem<SortCriteria>
|
||||
{
|
||||
private const float transition_duration = 100;
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
public override bool Active
|
||||
{
|
||||
get { return base.Active; }
|
||||
set
|
||||
{
|
||||
if (Active == value) return;
|
||||
|
||||
if (value)
|
||||
slideActive();
|
||||
else
|
||||
slideInactive();
|
||||
base.Active = value;
|
||||
}
|
||||
}
|
||||
|
||||
public SortTabItem(SortCriteria value) : base(value)
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Top = 8, Bottom = 8 },
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Text = (value as Enum).GetDescription() ?? value.ToString(),
|
||||
TextSize = 14,
|
||||
Font = @"Exo2.0-Bold",
|
||||
},
|
||||
box = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 5,
|
||||
Scale = new Vector2(1f, 0f),
|
||||
Colour = Color4.White,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
box.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
if (!Active)
|
||||
slideActive();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
if (!Active)
|
||||
slideInactive();
|
||||
}
|
||||
|
||||
private void slideActive()
|
||||
{
|
||||
box.ScaleTo(new Vector2(1f), transition_duration);
|
||||
}
|
||||
|
||||
private void slideInactive()
|
||||
{
|
||||
box.ScaleTo(new Vector2(1f, 0f), transition_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SortCriteria
|
||||
{
|
||||
Title,
|
||||
Artist,
|
||||
Creator,
|
||||
Difficulty,
|
||||
Ranked,
|
||||
Rating,
|
||||
}
|
||||
}
|
230
osu.Game/Overlays/DirectOverlay.cs
Normal file
230
osu.Game/Overlays/DirectOverlay.cs
Normal file
@ -0,0 +1,230 @@
|
||||
// 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 OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays.Direct;
|
||||
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class DirectOverlay : WaveOverlayContainer
|
||||
{
|
||||
public static readonly int WIDTH_PADDING = 80;
|
||||
private const float panel_padding = 10f;
|
||||
|
||||
private readonly FilterControl filter;
|
||||
private readonly FillFlowContainer resultCountsContainer;
|
||||
private readonly OsuSpriteText resultCountsText;
|
||||
private readonly FillFlowContainer<DirectPanel> panels;
|
||||
|
||||
private IEnumerable<BeatmapSetInfo> beatmapSets;
|
||||
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||
{
|
||||
get { return beatmapSets; }
|
||||
set
|
||||
{
|
||||
if (beatmapSets?.Equals(value) ?? false) return;
|
||||
beatmapSets = value;
|
||||
|
||||
recreatePanels(filter.DisplayStyle.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private ResultCounts resultAmounts;
|
||||
public ResultCounts ResultAmounts
|
||||
{
|
||||
get { return resultAmounts; }
|
||||
set
|
||||
{
|
||||
if (value == ResultAmounts) return;
|
||||
resultAmounts = value;
|
||||
|
||||
updateResultCounts();
|
||||
}
|
||||
}
|
||||
|
||||
public DirectOverlay()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// osu!direct colours are not part of the standard palette
|
||||
|
||||
FirstWaveColour = OsuColour.FromHex(@"19b0e2");
|
||||
SecondWaveColour = OsuColour.FromHex(@"2280a2");
|
||||
ThirdWaveColour = OsuColour.FromHex(@"005774");
|
||||
FourthWaveColour = OsuColour.FromHex(@"003a4e");
|
||||
|
||||
Header header;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.FromHex(@"485e74"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new[]
|
||||
{
|
||||
new Triangles
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TriangleScale = 5,
|
||||
ColourLight = OsuColour.FromHex(@"465b71"),
|
||||
ColourDark = OsuColour.FromHex(@"3f5265"),
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
|
||||
Children = new[]
|
||||
{
|
||||
new ScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollDraggerVisible = false,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
resultCountsContainer = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Found ",
|
||||
TextSize = 15,
|
||||
},
|
||||
resultCountsText = new OsuSpriteText
|
||||
{
|
||||
TextSize = 15,
|
||||
Font = @"Exo2.0-Bold",
|
||||
},
|
||||
}
|
||||
},
|
||||
panels = new FillFlowContainer<DirectPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
|
||||
Spacing = new Vector2(panel_padding),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filter = new FilterControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = Header.HEIGHT },
|
||||
},
|
||||
header = new Header
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
};
|
||||
|
||||
header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
|
||||
|
||||
filter.Search.Exit = Hide;
|
||||
filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
|
||||
filter.DisplayStyle.ValueChanged += recreatePanels;
|
||||
|
||||
updateResultCounts();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
resultCountsContainer.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
private void updateResultCounts()
|
||||
{
|
||||
resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
|
||||
if (ResultAmounts == null) return;
|
||||
|
||||
resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
|
||||
pluralize("Song", ResultAmounts.Songs) + ", " +
|
||||
pluralize("Tag", ResultAmounts.Tags);
|
||||
}
|
||||
|
||||
private string pluralize(string prefix, int value)
|
||||
{
|
||||
return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
|
||||
}
|
||||
|
||||
private void recreatePanels(PanelDisplayStyle displayStyle)
|
||||
{
|
||||
if (BeatmapSets == null) return;
|
||||
panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
|
||||
}
|
||||
|
||||
protected override bool OnFocus(InputState state)
|
||||
{
|
||||
filter.Search.TriggerFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
filter.Search.HoldFocus = true;
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
filter.Search.HoldFocus = false;
|
||||
}
|
||||
|
||||
public class ResultCounts
|
||||
{
|
||||
public readonly int Artists;
|
||||
public readonly int Songs;
|
||||
public readonly int Tags;
|
||||
|
||||
public ResultCounts(int artists, int songs, int tags)
|
||||
{
|
||||
Artists = artists;
|
||||
Songs = songs;
|
||||
Tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PanelDisplayStyle
|
||||
{
|
||||
Grid,
|
||||
List,
|
||||
}
|
||||
}
|
||||
}
|
@ -12,15 +12,24 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using OpenTK;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Users;
|
||||
using System.ComponentModel;
|
||||
using osu.Game.Graphics;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
public class LoginSettings : SettingsSubsection, IOnlineComponent
|
||||
public class LoginSettings : FillFlowContainer, IOnlineComponent
|
||||
{
|
||||
private bool bounding = true;
|
||||
private LoginForm form;
|
||||
private OsuColour colours;
|
||||
|
||||
protected override string Header => "Account";
|
||||
private UserPanel panel;
|
||||
private UserDropdown dropdown;
|
||||
|
||||
public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
|
||||
|
||||
@ -34,9 +43,18 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(APIAccess api)
|
||||
public LoginSettings()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
Spacing = new Vector2(0f, 5f);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(OsuColour colours, APIAccess api)
|
||||
{
|
||||
this.colours = colours;
|
||||
api?.Register(this);
|
||||
}
|
||||
|
||||
@ -49,6 +67,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
case APIState.Offline:
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "ACCOUNT",
|
||||
Margin = new MarginPadding { Bottom = 5 },
|
||||
Font = @"Exo2.0-Black",
|
||||
},
|
||||
form = new LoginForm()
|
||||
};
|
||||
break;
|
||||
@ -66,24 +90,73 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Connecting...",
|
||||
Margin = new MarginPadding { Top = 10, Bottom = 10 },
|
||||
},
|
||||
};
|
||||
break;
|
||||
case APIState.Online:
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = $"Connected as {api.Username}!",
|
||||
},
|
||||
new OsuButton
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Sign out",
|
||||
Action = api.Logout
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 20, Right = 20 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Signed in",
|
||||
TextSize = 18,
|
||||
Font = @"Exo2.0-Bold",
|
||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
||||
},
|
||||
},
|
||||
},
|
||||
panel = new UserPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X },
|
||||
dropdown = new UserDropdown { RelativeSizeAxes = Axes.X },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
panel.Status.BindTo(api.LocalUser.Value.Status);
|
||||
|
||||
dropdown.Current.ValueChanged += newValue =>
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case UserAction.Online:
|
||||
api.LocalUser.Value.Status.Value = new UserStatusOnline();
|
||||
dropdown.StatusColour = colours.Green;
|
||||
break;
|
||||
case UserAction.DoNotDisturb:
|
||||
api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
|
||||
dropdown.StatusColour = colours.Red;
|
||||
break;
|
||||
case UserAction.AppearOffline:
|
||||
api.LocalUser.Value.Status.Value = new UserStatusOffline();
|
||||
dropdown.StatusColour = colours.Gray7;
|
||||
break;
|
||||
case UserAction.SignOut:
|
||||
api.Logout();
|
||||
break;
|
||||
}
|
||||
};
|
||||
dropdown.Current.TriggerChange();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -170,5 +243,119 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
return base.OnFocus(state);
|
||||
}
|
||||
}
|
||||
|
||||
private class UserDropdown : OsuEnumDropdown<UserAction>
|
||||
{
|
||||
protected override DropdownHeader CreateHeader() => new UserDropdownHeader { AccentColour = AccentColour };
|
||||
protected override Menu CreateMenu() => new UserDropdownMenu();
|
||||
protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour };
|
||||
|
||||
public Color4 StatusColour
|
||||
{
|
||||
set
|
||||
{
|
||||
var h = Header as UserDropdownHeader;
|
||||
if (h == null) return;
|
||||
h.StatusColour = value;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.Gray5;
|
||||
}
|
||||
|
||||
private class UserDropdownHeader : OsuDropdownHeader
|
||||
{
|
||||
public const float LABEL_LEFT_MARGIN = 20;
|
||||
|
||||
private readonly TextAwesome statusIcon;
|
||||
public Color4 StatusColour
|
||||
{
|
||||
set
|
||||
{
|
||||
statusIcon.FadeColour(value, 500, EasingTypes.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
public UserDropdownHeader()
|
||||
{
|
||||
Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
|
||||
Margin = new MarginPadding { Bottom = 5 };
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Radius = 4,
|
||||
};
|
||||
|
||||
Icon.TextSize = 14;
|
||||
Icon.Margin = new MarginPadding(0);
|
||||
|
||||
Foreground.Add(statusIcon = new TextAwesome
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Icon = FontAwesome.fa_circle_o,
|
||||
TextSize = 14,
|
||||
});
|
||||
|
||||
Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Gray3;
|
||||
}
|
||||
}
|
||||
|
||||
private class UserDropdownMenu : OsuMenu
|
||||
{
|
||||
public UserDropdownMenu()
|
||||
{
|
||||
Margin = new MarginPadding { Bottom = 5 };
|
||||
CornerRadius = 5;
|
||||
ItemsContainer.Padding = new MarginPadding(0);
|
||||
Masking = true;
|
||||
EdgeEffect = new EdgeEffect
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Radius = 4,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Background.Colour = colours.Gray3;
|
||||
}
|
||||
}
|
||||
|
||||
private class UserDropdownMenuItem : OsuDropdownMenuItem
|
||||
{
|
||||
public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
|
||||
{
|
||||
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = UserDropdownHeader.LABEL_LEFT_MARGIN, Right = 5 };
|
||||
Chevron.Margin = new MarginPadding { Left = 2, Right = 3 };
|
||||
CornerRadius = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum UserAction
|
||||
{
|
||||
Online,
|
||||
[Description(@"Do not disturb")]
|
||||
DoNotDisturb,
|
||||
[Description(@"Appear offline")]
|
||||
AppearOffline,
|
||||
[Description(@"Sign out")]
|
||||
SignOut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user