1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 08:22:56 +08:00

Merge branch 'master' into fix-badge-container

This commit is contained in:
Dean Herbert 2018-04-23 19:13:34 +09:00 committed by GitHub
commit eb7fe0b9e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 220 additions and 66 deletions

View File

@ -1,4 +1,3 @@
# 2017-09-14
clone_depth: 1 clone_depth: 1
version: '{branch}-{build}' version: '{branch}-{build}'
image: Visual Studio 2017 image: Visual Studio 2017

View File

@ -1,4 +1,8 @@
# 2017-09-14 branches:
only:
- release
skip_tags: true
skip_branch_with_pr: true
clone_depth: 1 clone_depth: 1
version: '{branch}-{build}' version: '{branch}-{build}'
image: Visual Studio 2017 image: Visual Studio 2017

@ -1 +1 @@
Subproject commit 16e6a453db9a8f4454238a2911eb5f1444b7ec2a Subproject commit f1751c27ffe2c5febece74129368596b5ad3a4e2

View File

@ -7,6 +7,7 @@ using System.Configuration;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Management.Automation;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; using FileWebRequest = osu.Framework.IO.Network.FileWebRequest;
@ -99,6 +100,7 @@ namespace osu.Desktop.Deploy
write("Updating AssemblyInfo..."); write("Updating AssemblyInfo...");
updateCsprojVersion(version); updateCsprojVersion(version);
updateAppveyorVersion(version);
write("Running build process..."); write("Running build process...");
foreach (string targetName in TargetNames.Split(',')) foreach (string targetName in TargetNames.Split(','))
@ -404,6 +406,25 @@ namespace osu.Desktop.Deploy
Console.WriteLine(); Console.WriteLine();
} }
private static bool updateAppveyorVersion(string version)
{
try
{
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript($"Update-AppveyorBuild -Version \"{version}\"");
ps.Invoke();
}
return true;
}
catch
{
// we don't have appveyor and don't care
}
return false;
}
private static void write(string message, ConsoleColor col = ConsoleColor.Gray) private static void write(string message, ConsoleColor col = ConsoleColor.Gray)
{ {
if (sw.ElapsedMilliseconds > 0) if (sw.ElapsedMilliseconds > 0)

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" /> <Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<TargetFrameworks>net461</TargetFrameworks> <TargetFrameworks>net461</TargetFrameworks>
@ -14,5 +14,6 @@
<PackageReference Include="NUnit" Version="3.10.1" /> <PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="squirrel.windows" Version="1.7.8" Condition="'$(TargetFramework)' == 'net461'" /> <PackageReference Include="squirrel.windows" Version="1.7.8" Condition="'$(TargetFramework)' == 'net461'" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
<PackageReference Include="System.Management.Automation.dll" Version="10.0.10586" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,7 +7,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuEditPlayfield : OsuPlayfield public class OsuEditPlayfield : OsuPlayfield
{ {
protected override bool ProxyApproachCircles => false;
protected override bool DisplayJudgements => false; protected override bool DisplayJudgements => false;
} }
} }

View File

@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] Children = new[]
{ {
Background = new SpinnerBackground Background = new SpinnerBackground
{ {

View File

@ -21,9 +21,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer; private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly ConnectionRenderer<OsuHitObject> connectionLayer; private readonly ConnectionRenderer<OsuHitObject> connectionLayer;
// Todo: This should not be a thing, but is currently required for the editor
// https://github.com/ppy/osu-framework/issues/1283
protected virtual bool ProxyApproachCircles => true;
protected virtual bool DisplayJudgements => true; protected virtual bool DisplayJudgements => true;
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@ -59,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.UI
h.OnJudgement += onJudgement; h.OnJudgement += onJudgement;
var c = h as IDrawableHitObjectWithProxiedApproach; var c = h as IDrawableHitObjectWithProxiedApproach;
if (c != null && ProxyApproachCircles) if (c != null)
approachCircles.Add(c.ProxiedLayer.CreateProxy()); approachCircles.Add(c.ProxiedLayer.CreateProxy());
base.Add(h); base.Add(h);

View File

@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Formats
return output; return output;
} }
protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); protected abstract void ParseStreamInto(StreamReader stream, TOutput output);
} }
public abstract class Decoder public abstract class Decoder

View File

@ -13,15 +13,15 @@ namespace osu.Game.Beatmaps.Formats
AddDecoder<Beatmap>("{", m => new JsonBeatmapDecoder()); AddDecoder<Beatmap>("{", m => new JsonBeatmapDecoder());
} }
protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) protected override void ParseStreamInto(StreamReader stream, Beatmap output)
{ {
stream.BaseStream.Position = 0; stream.BaseStream.Position = 0;
stream.DiscardBufferedData(); stream.DiscardBufferedData();
stream.ReadToEnd().DeserializeInto(beatmap); stream.ReadToEnd().DeserializeInto(output);
foreach (var hitObject in beatmap.HitObjects) foreach (var hitObject in output.HitObjects)
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); hitObject.ApplyDefaults(output.ControlPointInfo, output.BeatmapInfo.BaseDifficulty);
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Formats
FormatVersion = version; FormatVersion = version;
} }
protected override void ParseStreamInto(StreamReader stream, T beatmap) protected override void ParseStreamInto(StreamReader stream, T output)
{ {
Section section = Section.None; Section section = Section.None;
@ -33,14 +33,21 @@ namespace osu.Game.Beatmaps.Formats
{ {
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
{ {
Logger.Log($"Unknown section \"{line}\" in {beatmap}"); Logger.Log($"Unknown section \"{line}\" in {output}");
section = Section.None; section = Section.None;
} }
continue; continue;
} }
ParseLine(beatmap, section, line); try
{
ParseLine(output, section, line);
}
catch (Exception e)
{
Logger.Error(e, $"Failed to process line \"{line}\" into {output}");
}
} }
} }

View File

@ -71,9 +71,9 @@ namespace osu.Game.Graphics.Containers
switch (linkType) switch (linkType)
{ {
case LinkAction.OpenBeatmap: case LinkAction.OpenBeatmap:
// todo: replace this with overlay.ShowBeatmap(id) once an appropriate API call is implemented. // TODO: proper query params handling
if (int.TryParse(linkArgument, out int beatmapId)) if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId))
Process.Start($"https://osu.ppy.sh/b/{beatmapId}"); game?.ShowBeatmap(beatmapId);
break; break;
case LinkAction.OpenBeatmapSet: case LinkAction.OpenBeatmapSet:
if (int.TryParse(linkArgument, out int setId)) if (int.TryParse(linkArgument, out int setId))

View File

@ -5,13 +5,21 @@ namespace osu.Game.Online.API.Requests
{ {
public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet> public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet>
{ {
private readonly int beatmapSetId; private readonly int id;
private readonly BeatmapSetLookupType type;
public GetBeatmapSetRequest(int beatmapSetId) public GetBeatmapSetRequest(int id, BeatmapSetLookupType type = BeatmapSetLookupType.SetId)
{ {
this.beatmapSetId = beatmapSetId; this.id = id;
this.type = type;
} }
protected override string Target => $@"beatmapsets/{beatmapSetId}"; protected override string Target => type == BeatmapSetLookupType.SetId ? $@"beatmapsets/{id}" : $@"beatmapsets/lookup?beatmap_id={id}";
}
public enum BeatmapSetLookupType
{
SetId,
BeatmapId,
} }
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests
public class GetMessagesRequest : APIRequest<List<Message>> public class GetMessagesRequest : APIRequest<List<Message>>
{ {
private readonly List<Channel> channels; private readonly List<Channel> channels;
private long? since; private readonly long? since;
public GetMessagesRequest(List<Channel> channels, long? sinceId) public GetMessagesRequest(List<Channel> channels, long? sinceId)
{ {

View File

@ -7,7 +7,7 @@ namespace osu.Game.Online.API.Requests
{ {
public class GetUserRequest : APIRequest<User> public class GetUserRequest : APIRequest<User>
{ {
private long? userId; private readonly long? userId;
public GetUserRequest(long? userId = null) public GetUserRequest(long? userId = null)
{ {

View File

@ -166,6 +166,12 @@ namespace osu.Game
/// <param name="userId">The user to display.</param> /// <param name="userId">The user to display.</param>
public void ShowUser(long userId) => userProfile.ShowUser(userId); public void ShowUser(long userId) => userProfile.ShowUser(userId);
/// <summary>
/// Show a beatmap's set as an overlay, displaying the given beatmap.
/// </summary>
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
protected void LoadScore(Score s) protected void LoadScore(Score s)
{ {
scoreLoad?.Cancel(); scoreLoad?.Cancel();

View File

@ -17,21 +17,39 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.BeatmapSet.Scores;
using System.Linq;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
public class BeatmapSetOverlay : WaveOverlayContainer public class BeatmapSetOverlay : WaveOverlayContainer
{ {
private const int fade_duration = 300;
public const float X_PADDING = 40; public const float X_PADDING = 40;
public const float RIGHT_WIDTH = 275; public const float RIGHT_WIDTH = 275;
private readonly Header header; private readonly Header header;
private readonly Info info; private readonly Info info;
private APIAccess api; private APIAccess api;
private RulesetStore rulesets; private RulesetStore rulesets;
private readonly ScrollContainer scroll; private readonly ScrollContainer scroll;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get => beatmapSet;
set
{
if (value == beatmapSet)
return;
header.BeatmapSet = info.BeatmapSet = beatmapSet = value;
}
}
// receive input outside our bounds so we can trigger a close event on ourselves. // receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
@ -107,7 +125,8 @@ namespace osu.Game.Overlays
{ {
base.PopOut(); base.PopOut();
header.Details.StopPreview(); header.Details.StopPreview();
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out);
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null);
} }
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
@ -116,17 +135,31 @@ namespace osu.Game.Overlays
return true; return true;
} }
public void FetchAndShowBeatmap(int beatmapId)
{
BeatmapSet = null;
var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
req.Success += res =>
{
ShowBeatmapSet(res.ToBeatmapSet(rulesets));
header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId);
};
api.Queue(req);
Show();
}
public void FetchAndShowBeatmapSet(int beatmapSetId) public void FetchAndShowBeatmapSet(int beatmapSetId)
{ {
// todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. BeatmapSet = null;
var req = new GetBeatmapSetRequest(beatmapSetId); var req = new GetBeatmapSetRequest(beatmapSetId);
req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets));
api.Queue(req); api.Queue(req);
Show();
} }
public void ShowBeatmapSet(BeatmapSetInfo set) public void ShowBeatmapSet(BeatmapSetInfo set)
{ {
header.BeatmapSet = info.BeatmapSet = set; BeatmapSet = set;
Show(); Show();
scroll.ScrollTo(0); scroll.ScrollTo(0);
} }

View File

@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
base.UpdateAfterChildrenLife(); base.UpdateAfterChildrenLife();
// We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions
speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); speedChangeVisualiser.UpdatePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize);
} }
} }
} }

View File

@ -10,24 +10,23 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
public interface ISpeedChangeVisualiser public interface ISpeedChangeVisualiser
{ {
/// <summary> /// <summary>
/// Computes the states of <see cref="DrawableHitObject"/>s that are constant, such as lifetime and spatial length. /// Computes the states of <see cref="DrawableHitObject"/>s that remain constant while scrolling, such as lifetime and spatial length.
/// This is invoked once whenever <paramref name="timeRange"/> or <paramref name="length"/> changes. /// This is invoked once whenever <paramref name="timeRange"/> or <paramref name="length"/> changes.
/// </summary> /// </summary>
/// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose states should be computed.</param> /// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose states should be computed.</param>
/// <param name="direction">The scrolling direction.</param> /// <param name="direction">The scrolling direction.</param>
/// <param name="timeRange">The duration required to scroll through one length of the screen before any control point adjustments.</param> /// <param name="timeRange">The duration required to scroll through one length of the screen before any speed adjustments.</param>
/// <param name="length">The length of the screen that is scrolled through.</param> /// <param name="length">The length of the screen that is scrolled through.</param>
void ComputeInitialStates(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); void ComputeInitialStates(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length);
/// <summary> /// <summary>
/// Computes the states of <see cref="DrawableHitObject"/>s that change depending on <paramref name="currentTime"/>, such as position. /// Updates the positions of <see cref="DrawableHitObject"/>s, depending on the current time. This is invoked once per frame.
/// This is invoked once per frame.
/// </summary> /// </summary>
/// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose states should be computed.</param> /// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose positions should be computed.</param>
/// <param name="direction">The scrolling direction.</param> /// <param name="direction">The scrolling direction.</param>
/// <param name="currentTime">The current time.</param> /// <param name="currentTime">The current time.</param>
/// <param name="timeRange">The duration required to scroll through one length of the screen before any control point adjustments.</param> /// <param name="timeRange">The duration required to scroll through one length of the screen before any speed adjustments.</param>
/// <param name="length">The length of the screen that is scrolled through.</param> /// <param name="length">The length of the screen that is scrolled through.</param>
void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); void UpdatePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length);
} }
} }

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.Timing;
using OpenTK; using OpenTK;
@ -22,24 +23,45 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
{ {
foreach (var obj in hitObjects) foreach (var obj in hitObjects)
{ {
var controlPoint = controlPointAt(obj.HitObject.StartTime); // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases
obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; double visibleDuration = timeRange / controlPointAt(obj.HitObject.StartTime).Multiplier;
obj.LifetimeStart = obj.HitObject.StartTime - visibleDuration;
if (obj.HitObject is IHasEndTime endTime)
{
// At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin.
// This results in a negative-position value, and the absolute of it indicates the length of the hitobject.
var hitObjectLength = -hitObjectPositionAt(obj, endTime.EndTime, timeRange);
switch (direction)
{
case ScrollingDirection.Up:
case ScrollingDirection.Down:
obj.Height = (float)(hitObjectLength * length.Y);
break;
case ScrollingDirection.Left:
case ScrollingDirection.Right:
obj.Width = (float)(hitObjectLength * length.X);
break;
}
}
if (obj.HasNestedHitObjects) if (obj.HasNestedHitObjects)
{ {
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
// Nested hitobjects don't need to scroll, but they do need accurate positions
UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
} }
} }
} }
public void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) public void UpdatePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length)
{ {
foreach (var obj in hitObjects) foreach (var obj in hitObjects)
{ {
var controlPoint = controlPointAt(obj.HitObject.StartTime); var position = hitObjectPositionAt(obj, currentTime, timeRange);
var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange;
switch (direction) switch (direction)
{ {
@ -59,7 +81,28 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
} }
} }
/// <summary>
/// Computes the position of a <see cref="DrawableHitObject"/> at a point in time.
/// <para>
/// At t &lt; startTime, position &gt; 0. <br />
/// At t = startTime, position = 0. <br />
/// At t &gt; startTime, position &lt; 0.
/// </para>
/// </summary>
/// <param name="obj">The <see cref="DrawableHitObject"/>.</param>
/// <param name="time">The time to find the position of <paramref name="obj"/> at.</param>
/// <param name="timeRange">The amount of time visualised by the scrolling area.</param>
/// <returns>The position of <paramref name="obj"/> in the scrolling area at time = <paramref name="time"/>.</returns>
private double hitObjectPositionAt(DrawableHitObject obj, double time, double timeRange)
=> (obj.HitObject.StartTime - time) / timeRange * controlPointAt(obj.HitObject.StartTime).Multiplier;
private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint();
/// <summary>
/// Finds the <see cref="MultiplierControlPoint"/> which affects the speed of hitobjects at a specific time.
/// </summary>
/// <param name="time">The time which the <see cref="MultiplierControlPoint"/> should affect.</param>
/// <returns>The <see cref="MultiplierControlPoint"/>.</returns>
private MultiplierControlPoint controlPointAt(double time) private MultiplierControlPoint controlPointAt(double time)
{ {
if (controlPoints.Count == 0) if (controlPoints.Count == 0)

View File

@ -25,23 +25,25 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
{ {
foreach (var obj in hitObjects) foreach (var obj in hitObjects)
{ {
// To reduce iterations when updating hitobject positions later on, their initial positions are cached
var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange);
// Todo: This is approximate and will be incorrect in the case of extreme speed changes
obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000;
if (obj.HitObject is IHasEndTime endTime) if (obj.HitObject is IHasEndTime endTime)
{ {
var diff = positionAt(endTime.EndTime, timeRange) - startPosition; var hitObjectLength = positionAt(endTime.EndTime, timeRange) - startPosition;
switch (direction) switch (direction)
{ {
case ScrollingDirection.Up: case ScrollingDirection.Up:
case ScrollingDirection.Down: case ScrollingDirection.Down:
obj.Height = (float)(diff * length.Y); obj.Height = (float)(hitObjectLength * length.Y);
break; break;
case ScrollingDirection.Left: case ScrollingDirection.Left:
case ScrollingDirection.Right: case ScrollingDirection.Right:
obj.Width = (float)(diff * length.X); obj.Width = (float)(hitObjectLength * length.X);
break; break;
} }
} }
@ -49,12 +51,14 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
if (obj.HasNestedHitObjects) if (obj.HasNestedHitObjects)
{ {
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
// Nested hitobjects don't need to scroll, but they do need accurate positions
UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
} }
} }
} }
public void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) public void UpdatePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length)
{ {
var timelinePosition = positionAt(currentTime, timeRange); var timelinePosition = positionAt(currentTime, timeRange);
@ -80,21 +84,38 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
} }
} }
/// <summary>
/// Finds the position which corresponds to a point in time.
/// This is a non-linear operation that depends on all the control points up to and including the one active at the time value.
/// </summary>
/// <param name="time">The time to find the position at.</param>
/// <param name="timeRange">The amount of time visualised by the scrolling area.</param>
/// <returns>A positive value indicating the position at <paramref name="time"/>.</returns>
private double positionAt(double time, double timeRange) private double positionAt(double time, double timeRange)
{ {
double length = 0; double length = 0;
// We need to consider all timing points until the specified time and not just the currently-active one,
// since each timing point individually affects the positions of _all_ hitobjects after its start time
for (int i = 0; i < controlPoints.Count; i++) for (int i = 0; i < controlPoints.Count; i++)
{ {
var current = controlPoints[i]; var current = controlPoints[i];
var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null;
// We don't need to consider any control points beyond the current time, since it will not yet
// affect any hitobjects
if (i > 0 && current.StartTime > time) if (i > 0 && current.StartTime > time)
continue; continue;
// Duration of the current control point // Duration of the current control point
var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime;
length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; // We want to consider the minimal amount of time that this control point has affected,
// which may be either its duration, or the amount of time that has passed within it
var durationInCurrent = Math.Min(currentDuration, time - current.StartTime);
// Figure out how much of the time range the duration represents, and adjust it by the speed multiplier
length += durationInCurrent / timeRange * current.Multiplier;
} }
return length; return length;

View File

@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
var dragLayer = new DragLayer(maskContainer.Select); var dragLayer = new DragLayer(maskContainer.Select);
dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); dragLayer.DragEnd += () => maskSelection.UpdateVisibility();
InternalChildren = new Drawable[] InternalChildren = new[]
{ {
dragLayer, dragLayer,
maskSelection, maskSelection,

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
@ -43,6 +44,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
public override void Add(HitObjectMask drawable) public override void Add(HitObjectMask drawable)
{ {
if (drawable == null) throw new ArgumentNullException(nameof(drawable));
base.Add(drawable); base.Add(drawable);
drawable.Selected += onMaskSelected; drawable.Selected += onMaskSelected;
@ -51,8 +54,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
drawable.DragRequested += onDragRequested; drawable.DragRequested += onDragRequested;
} }
public override bool Remove(HitObjectMask drawable) public override bool Remove([NotNull] HitObjectMask drawable)
{ {
if (drawable == null) throw new ArgumentNullException(nameof(drawable));
var result = base.Remove(drawable); var result = base.Remove(drawable);
if (result) if (result)

View File

@ -39,6 +39,7 @@ namespace osu.Game.Screens.Menu
private Bindable<bool> menuVoice; private Bindable<bool> menuVoice;
private Bindable<bool> menuMusic; private Bindable<bool> menuMusic;
private Track track; private Track track;
private WorkingBeatmap beatmap;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
@ -69,9 +70,8 @@ namespace osu.Game.Screens.Menu
} }
} }
Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
track = beatmap.Track;
track = Beatmap.Value.Track;
welcome = audio.Sample.Get(@"welcome"); welcome = audio.Sample.Get(@"welcome");
seeya = audio.Sample.Get(@"seeya"); seeya = audio.Sample.Get(@"seeya");
@ -81,6 +81,8 @@ namespace osu.Game.Screens.Menu
{ {
base.OnEntering(last); base.OnEntering(last);
Game.Beatmap.Value = beatmap;
if (menuVoice) if (menuVoice)
welcome.Play(); welcome.Play();

View File

@ -223,11 +223,11 @@ namespace osu.Game.Screens.Ranking
private class DateTimeDisplay : Container private class DateTimeDisplay : Container
{ {
private DateTime datetime; private readonly DateTime date;
public DateTimeDisplay(DateTime datetime) public DateTimeDisplay(DateTime date)
{ {
this.datetime = datetime; this.date = date;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
@ -251,7 +251,7 @@ namespace osu.Game.Screens.Ranking
{ {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Text = datetime.ToShortDateString(), Text = date.ToShortDateString(),
Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
}, },
@ -259,7 +259,7 @@ namespace osu.Game.Screens.Ranking
{ {
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Text = datetime.ToShortTimeString(), Text = date.ToShortTimeString(),
Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Colour = Color4.White, Colour = Color4.White,
} }

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -35,6 +36,8 @@ namespace osu.Game.Screens.Select.Carousel
private Triangles triangles; private Triangles triangles;
private StarCounter starCounter; private StarCounter starCounter;
private BeatmapSetOverlay beatmapOverlay;
public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel)
{ {
beatmap = panel.Beatmap; beatmap = panel.Beatmap;
@ -42,8 +45,10 @@ namespace osu.Game.Screens.Select.Carousel
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(SongSelect songSelect, BeatmapManager manager) private void load(SongSelect songSelect, BeatmapManager manager, BeatmapSetOverlay beatmapOverlay)
{ {
this.beatmapOverlay = beatmapOverlay;
if (songSelect != null) if (songSelect != null)
{ {
startRequested = songSelect.FinaliseSelection; startRequested = songSelect.FinaliseSelection;
@ -171,6 +176,10 @@ namespace osu.Game.Screens.Select.Carousel
new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)),
new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)),
new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)),
new OsuMenuItem("Details", MenuItemType.Standard, () =>
{
if (beatmap.OnlineBeatmapID.HasValue) beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value);
}),
}; };
} }
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Skinning
{ {
} }
protected override void ParseLine(SkinConfiguration output, Section section, string line) protected override void ParseLine(SkinConfiguration skin, Section section, string line)
{ {
switch (section) switch (section)
{ {
@ -22,17 +22,17 @@ namespace osu.Game.Skinning
switch (pair.Key) switch (pair.Key)
{ {
case @"Name": case @"Name":
output.SkinInfo.Name = pair.Value; skin.SkinInfo.Name = pair.Value;
break; break;
case @"Author": case @"Author":
output.SkinInfo.Creator = pair.Value; skin.SkinInfo.Creator = pair.Value;
break; break;
} }
break; break;
} }
base.ParseLine(output, section, line); base.ParseLine(skin, section, line);
} }
} }
} }