mirror of
https://github.com/osukey/osukey.git
synced 2025-05-29 09:27:18 +09:00
Merge branch 'master' into fix-editor-batch-handling
This commit is contained in:
commit
ecfb7e94c5
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1004.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1009.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -29,6 +29,11 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether an update has been downloaded but not yet applied.
|
||||||
|
/// </summary>
|
||||||
|
private bool updatePending;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification)
|
private void load(NotificationOverlay notification)
|
||||||
{
|
{
|
||||||
@ -37,9 +42,9 @@ namespace osu.Desktop.Updater
|
|||||||
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
|
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync();
|
||||||
|
|
||||||
private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
|
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
|
||||||
{
|
{
|
||||||
// should we schedule a retry on completion of this check?
|
// should we schedule a retry on completion of this check?
|
||||||
bool scheduleRecheck = true;
|
bool scheduleRecheck = true;
|
||||||
@ -49,9 +54,19 @@ namespace osu.Desktop.Updater
|
|||||||
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
||||||
|
|
||||||
if (info.ReleasesToApply.Count == 0)
|
if (info.ReleasesToApply.Count == 0)
|
||||||
|
{
|
||||||
|
if (updatePending)
|
||||||
|
{
|
||||||
|
// the user may have dismissed the completion notice, so show it again.
|
||||||
|
notificationOverlay.Post(new UpdateCompleteNotification(this));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// no updates available. bail and retry later.
|
// no updates available. bail and retry later.
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
@ -72,6 +87,7 @@ namespace osu.Desktop.Updater
|
|||||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
|
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
updatePending = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -103,6 +119,8 @@ namespace osu.Desktop.Updater
|
|||||||
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
|
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -111,10 +129,27 @@ namespace osu.Desktop.Updater
|
|||||||
updateManager?.Dispose();
|
updateManager?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class UpdateCompleteNotification : ProgressCompletionNotification
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
|
||||||
|
{
|
||||||
|
Text = @"Update ready to install. Click to restart!";
|
||||||
|
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
updateManager.PrepareUpdateAsync()
|
||||||
|
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class UpdateProgressNotification : ProgressNotification
|
private class UpdateProgressNotification : ProgressNotification
|
||||||
{
|
{
|
||||||
private readonly SquirrelUpdateManager updateManager;
|
private readonly SquirrelUpdateManager updateManager;
|
||||||
private OsuGame game;
|
|
||||||
|
|
||||||
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
||||||
{
|
{
|
||||||
@ -123,23 +158,12 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
protected override Notification CreateCompletionNotification()
|
protected override Notification CreateCompletionNotification()
|
||||||
{
|
{
|
||||||
return new ProgressCompletionNotification
|
return new UpdateCompleteNotification(updateManager);
|
||||||
{
|
|
||||||
Text = @"Update ready to install. Click to restart!",
|
|
||||||
Activated = () =>
|
|
||||||
{
|
|
||||||
updateManager.PrepareUpdateAsync()
|
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, OsuGame game)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
this.game = game;
|
|
||||||
|
|
||||||
IconContent.AddRange(new Drawable[]
|
IconContent.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
|
@ -141,6 +141,35 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Great,
|
||||||
|
|
||||||
|
HitResult.LargeTickHit,
|
||||||
|
HitResult.SmallTickHit,
|
||||||
|
HitResult.LargeBonus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return "large droplet";
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return "small droplet";
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return "banana";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
|
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
|
||||||
|
@ -319,6 +319,31 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Perfect,
|
||||||
|
HitResult.Great,
|
||||||
|
HitResult.Good,
|
||||||
|
HitResult.Ok,
|
||||||
|
HitResult.Meh,
|
||||||
|
|
||||||
|
HitResult.LargeTickHit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return "hold tick";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
new StatisticRow
|
||||||
|
@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
OriginPosition = body.PathOffset;
|
OriginPosition = body.PathOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RecyclePath() => body.RecyclePath();
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||||
{
|
{
|
||||||
protected readonly SliderBodyPiece BodyPiece;
|
protected SliderBodyPiece BodyPiece { get; private set; }
|
||||||
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
|
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; }
|
||||||
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
|
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; }
|
||||||
protected readonly PathControlPointVisualiser ControlPointVisualiser;
|
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
|
||||||
|
|
||||||
|
private readonly DrawableSlider slider;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
@ -44,17 +46,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
var sliderObject = (Slider)slider.HitObject;
|
this.slider = slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||||
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true)
|
|
||||||
{
|
|
||||||
RemoveControlPointsRequested = removeControlPoints
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,13 +68,35 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
||||||
pathVersion.BindValueChanged(_ => updatePath());
|
pathVersion.BindValueChanged(_ => updatePath());
|
||||||
|
|
||||||
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
BodyPiece.UpdateFrom(HitObject);
|
if (IsSelected)
|
||||||
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSelected()
|
||||||
|
{
|
||||||
|
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser((Slider)slider.HitObject, true)
|
||||||
|
{
|
||||||
|
RemoveControlPointsRequested = removeControlPoints
|
||||||
|
});
|
||||||
|
|
||||||
|
base.OnSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDeselected()
|
||||||
|
{
|
||||||
|
base.OnDeselected();
|
||||||
|
|
||||||
|
// throw away frame buffers on deselection.
|
||||||
|
ControlPointVisualiser?.Expire();
|
||||||
|
BodyPiece.RecyclePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 rightClickPosition;
|
private Vector2 rightClickPosition;
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -25,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
SelectionBox.CanRotate = canOperate;
|
SelectionBox.CanRotate = canOperate;
|
||||||
SelectionBox.CanScaleX = canOperate;
|
SelectionBox.CanScaleX = canOperate;
|
||||||
SelectionBox.CanScaleY = canOperate;
|
SelectionBox.CanScaleY = canOperate;
|
||||||
|
SelectionBox.CanReverse = canOperate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnOperationEnded()
|
protected override void OnOperationEnded()
|
||||||
@ -41,6 +43,54 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Vector2? referenceOrigin;
|
private Vector2? referenceOrigin;
|
||||||
|
|
||||||
|
public override bool HandleReverse()
|
||||||
|
{
|
||||||
|
var hitObjects = selectedMovableObjects;
|
||||||
|
|
||||||
|
double endTime = hitObjects.Max(h => h.GetEndTime());
|
||||||
|
double startTime = hitObjects.Min(h => h.StartTime);
|
||||||
|
|
||||||
|
bool moreThanOneObject = hitObjects.Length > 1;
|
||||||
|
|
||||||
|
foreach (var h in hitObjects)
|
||||||
|
{
|
||||||
|
if (moreThanOneObject)
|
||||||
|
h.StartTime = endTime - (h.GetEndTime() - startTime);
|
||||||
|
|
||||||
|
if (h is Slider slider)
|
||||||
|
{
|
||||||
|
var points = slider.Path.ControlPoints.ToArray();
|
||||||
|
Vector2 endPos = points.Last().Position.Value;
|
||||||
|
|
||||||
|
slider.Path.ControlPoints.Clear();
|
||||||
|
|
||||||
|
slider.Position += endPos;
|
||||||
|
|
||||||
|
PathType? lastType = null;
|
||||||
|
|
||||||
|
for (var i = 0; i < points.Length; i++)
|
||||||
|
{
|
||||||
|
var p = points[i];
|
||||||
|
p.Position.Value -= endPos;
|
||||||
|
|
||||||
|
// propagate types forwards to last null type
|
||||||
|
if (i == points.Length - 1)
|
||||||
|
p.Type.Value = lastType;
|
||||||
|
else if (p.Type.Value != null)
|
||||||
|
{
|
||||||
|
var newType = p.Type.Value;
|
||||||
|
p.Type.Value = lastType;
|
||||||
|
lastType = newType;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider.Path.ControlPoints.Insert(0, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool HandleFlip(Direction direction)
|
public override bool HandleFlip(Direction direction)
|
||||||
{
|
{
|
||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
|
@ -42,7 +42,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
private double lastSliderHeadFadeOutStartTime;
|
private double lastSliderHeadFadeOutStartTime;
|
||||||
private double lastSliderHeadFadeOutDuration;
|
private double lastSliderHeadFadeOutDuration;
|
||||||
|
|
||||||
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
|
protected override void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, true);
|
||||||
|
|
||||||
|
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, false);
|
||||||
|
|
||||||
|
private void applyState(DrawableHitObject drawable, bool increaseVisibility)
|
||||||
{
|
{
|
||||||
if (!(drawable is DrawableOsuHitObject d))
|
if (!(drawable is DrawableOsuHitObject d))
|
||||||
return;
|
return;
|
||||||
@ -86,14 +90,23 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
lastSliderHeadFadeOutStartTime = fadeOutStartTime;
|
lastSliderHeadFadeOutStartTime = fadeOutStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't want to see the approach circle
|
Drawable fadeTarget = circle;
|
||||||
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
|
||||||
circle.ApproachCircle.Hide();
|
if (increaseVisibility)
|
||||||
|
{
|
||||||
|
// only fade the circle piece (not the approach circle) for the increased visibility object.
|
||||||
|
fadeTarget = circle.CirclePiece;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we don't want to see the approach circle
|
||||||
|
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||||
|
circle.ApproachCircle.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
// fade out immediately after fade in.
|
// fade out immediately after fade in.
|
||||||
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
||||||
circle.FadeOut(fadeOutDuration);
|
fadeTarget.FadeOut(fadeOutDuration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
|
@ -191,6 +191,41 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Great,
|
||||||
|
HitResult.Ok,
|
||||||
|
HitResult.Meh,
|
||||||
|
|
||||||
|
HitResult.LargeTickHit,
|
||||||
|
HitResult.SmallTickHit,
|
||||||
|
HitResult.SmallBonus,
|
||||||
|
HitResult.LargeBonus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return "slider tick";
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return "slider end";
|
||||||
|
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
return "spinner spin";
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return "spinner bonus";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
||||||
{
|
{
|
||||||
TaikoHitObject first = x.First();
|
TaikoHitObject first = x.First();
|
||||||
if (x.Skip(1).Any() && !(first is Swell))
|
if (x.Skip(1).Any() && first.CanBeStrong)
|
||||||
first.IsStrong = true;
|
first.IsStrong = true;
|
||||||
return first;
|
return first;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
@ -89,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
|
yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool HandleMovement(MoveSelectionEvent moveEvent) => true;
|
||||||
|
|
||||||
protected override void UpdateTernaryStates()
|
protected override void UpdateTernaryStates()
|
||||||
{
|
{
|
||||||
base.UpdateTernaryStates();
|
base.UpdateTernaryStates();
|
||||||
|
@ -158,7 +158,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.LoadSamples();
|
base.LoadSamples();
|
||||||
|
|
||||||
isStrong.Value = getStrongSamples().Any();
|
if (HitObject.CanBeStrong)
|
||||||
|
isStrong.Value = getStrongSamples().Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSamplesFromStrong()
|
private void updateSamplesFromStrong()
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
set => Duration = value - StartTime;
|
set => Duration = value - StartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanBeStrong => false;
|
||||||
|
|
||||||
public double Duration { get; set; }
|
public double Duration { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -30,6 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
|
|
||||||
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
|
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="TaikoHitObject"/> can be made a "strong" (large) hit.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanBeStrong => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this HitObject is a "strong" type.
|
/// Whether this HitObject is a "strong" type.
|
||||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||||
@ -37,7 +43,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
public bool IsStrong
|
public bool IsStrong
|
||||||
{
|
{
|
||||||
get => IsStrongBindable.Value;
|
get => IsStrongBindable.Value;
|
||||||
set => IsStrongBindable.Value = value;
|
set
|
||||||
|
{
|
||||||
|
if (value && !CanBeStrong)
|
||||||
|
throw new InvalidOperationException($"Object of type {GetType()} cannot be strong");
|
||||||
|
|
||||||
|
IsStrongBindable.Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
@ -159,6 +159,33 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Great,
|
||||||
|
HitResult.Ok,
|
||||||
|
|
||||||
|
HitResult.SmallTickHit,
|
||||||
|
|
||||||
|
HitResult.SmallBonus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return "drum tick";
|
||||||
|
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
return "strong bonus";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||||
|
@ -193,6 +193,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
|
|
||||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
|
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
|
||||||
{
|
{
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
User = new User { Username = "osu!" },
|
User = new User { Username = "osu!" },
|
||||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
|
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
|
||||||
})));
|
})));
|
||||||
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
@ -13,7 +12,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneTimelineTickDisplay : TimelineTestScene
|
public class TestSceneTimelineTickDisplay : TimelineTestScene
|
||||||
{
|
{
|
||||||
public override Drawable CreateTestComponent() => new TimelineTickDisplay();
|
public override Drawable CreateTestComponent() => Empty(); // tick display is implicitly inside the timeline.
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -76,7 +77,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => graph.ResampledWaveform != null);
|
AddUntilStep("wait for load", () => graph.Loaded.IsSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -98,12 +99,18 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => graph.ResampledWaveform != null);
|
AddUntilStep("wait for load", () => graph.Loaded.IsSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestWaveformGraph : WaveformGraph
|
public class TestWaveformGraph : WaveformGraph
|
||||||
{
|
{
|
||||||
public new Waveform ResampledWaveform => base.ResampledWaveform;
|
public readonly ManualResetEventSlim Loaded = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
protected override void OnWaveformRegenerated(Waveform waveform)
|
||||||
|
{
|
||||||
|
base.OnWaveformRegenerated(waveform);
|
||||||
|
Loaded.Set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
@ -23,33 +24,41 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivation()
|
public void TestGameplayOverlayActivation()
|
||||||
{
|
{
|
||||||
|
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivationPaused()
|
public void TestGameplayOverlayActivationPaused()
|
||||||
{
|
{
|
||||||
|
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
AddStep("pause gameplay", () => Player.Pause());
|
AddStep("pause gameplay", () => Player.Pause());
|
||||||
|
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
|
||||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivationReplayLoaded()
|
public void TestGameplayOverlayActivationReplayLoaded()
|
||||||
{
|
{
|
||||||
|
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
|
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
|
||||||
|
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
|
||||||
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGameplayOverlayActivationBreaks()
|
public void TestGameplayOverlayActivationBreaks()
|
||||||
{
|
{
|
||||||
|
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
|
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
|
||||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||||
|
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
|
||||||
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
|
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
|
||||||
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||||
|
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
|
||||||
@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected class OverlayTestPlayer : TestPlayer
|
protected class OverlayTestPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
|
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
|
||||||
|
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,12 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
createTest(CreateDistributedHitEvents());
|
createTest(CreateDistributedHitEvents());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAroundCentre()
|
||||||
|
{
|
||||||
|
createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestZeroTimeOffset()
|
public void TestZeroTimeOffset()
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Configuration.Tracking;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Input;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -69,6 +70,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
Set(OsuSetting.MouseDisableButtons, false);
|
Set(OsuSetting.MouseDisableButtons, false);
|
||||||
Set(OsuSetting.MouseDisableWheel, false);
|
Set(OsuSetting.MouseDisableWheel, false);
|
||||||
|
Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
Set(OsuSetting.ShowFpsDisplay, false);
|
Set(OsuSetting.ShowFpsDisplay, false);
|
||||||
@ -194,6 +196,7 @@ namespace osu.Game.Configuration
|
|||||||
FadePlayfieldWhenHealthLow,
|
FadePlayfieldWhenHealthLow,
|
||||||
MouseDisableButtons,
|
MouseDisableButtons,
|
||||||
MouseDisableWheel,
|
MouseDisableWheel,
|
||||||
|
ConfineMouseMode,
|
||||||
AudioOffset,
|
AudioOffset,
|
||||||
VolumeInactive,
|
VolumeInactive,
|
||||||
MenuMusic,
|
MenuMusic,
|
||||||
|
61
osu.Game/Input/ConfineMouseTracker.cs
Normal file
61
osu.Game/Input/ConfineMouseTracker.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Input
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connects <see cref="OsuSetting.ConfineMouseMode"/> with <see cref="FrameworkSetting.ConfineMouseMode"/>.
|
||||||
|
/// If <see cref="OsuGame.LocalUserPlaying"/> is true, we should also confine the mouse cursor if it has been
|
||||||
|
/// requested with <see cref="OsuConfineMouseMode.DuringGameplay"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ConfineMouseTracker : Component
|
||||||
|
{
|
||||||
|
private Bindable<ConfineMouseMode> frameworkConfineMode;
|
||||||
|
private Bindable<OsuConfineMouseMode> osuConfineMode;
|
||||||
|
private IBindable<bool> localUserPlaying;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
|
||||||
|
{
|
||||||
|
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
|
||||||
|
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
|
||||||
|
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
|
||||||
|
|
||||||
|
osuConfineMode.ValueChanged += _ => updateConfineMode();
|
||||||
|
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfineMode()
|
||||||
|
{
|
||||||
|
// confine mode is unavailable on some platforms
|
||||||
|
if (frameworkConfineMode.Disabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (osuConfineMode.Value)
|
||||||
|
{
|
||||||
|
case OsuConfineMouseMode.Never:
|
||||||
|
frameworkConfineMode.Value = ConfineMouseMode.Never;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OsuConfineMouseMode.Fullscreen:
|
||||||
|
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OsuConfineMouseMode.DuringGameplay:
|
||||||
|
frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OsuConfineMouseMode.Always:
|
||||||
|
frameworkConfineMode.Value = ConfineMouseMode.Always;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
osu.Game/Input/OsuConfineMouseMode.cs
Normal file
37
osu.Game/Input/OsuConfineMouseMode.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Input
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the situations in which the mouse cursor should be confined to the window.
|
||||||
|
/// Expands upon <see cref="ConfineMouseMode"/> by providing the option to confine during gameplay.
|
||||||
|
/// </summary>
|
||||||
|
public enum OsuConfineMouseMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse cursor will be free to move outside the game window.
|
||||||
|
/// </summary>
|
||||||
|
Never,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse cursor will be locked to the window bounds while in fullscreen mode.
|
||||||
|
/// </summary>
|
||||||
|
Fullscreen,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse cursor will be locked to the window bounds during gameplay,
|
||||||
|
/// but may otherwise move freely.
|
||||||
|
/// </summary>
|
||||||
|
[Description("During Gameplay")]
|
||||||
|
DuringGameplay,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mouse cursor will always be locked to the window bounds while the game has focus.
|
||||||
|
/// </summary>
|
||||||
|
Always
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,15 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
|
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the local user is currently interacting with the game in a way that should not be interrupted.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is exclusively managed by <see cref="Player"/>. If other components are mutating this state, a more
|
||||||
|
/// resilient method should be used to ensure correct state.
|
||||||
|
/// </remarks>
|
||||||
|
public Bindable<bool> LocalUserPlaying = new BindableBool();
|
||||||
|
|
||||||
protected OsuScreenStack ScreenStack;
|
protected OsuScreenStack ScreenStack;
|
||||||
|
|
||||||
protected BackButton BackButton;
|
protected BackButton BackButton;
|
||||||
@ -577,7 +586,8 @@ namespace osu.Game
|
|||||||
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
idleTracker
|
idleTracker,
|
||||||
|
new ConfineMouseTracker()
|
||||||
});
|
});
|
||||||
|
|
||||||
ScreenStack.ScreenPushed += screenPushed;
|
ScreenStack.ScreenPushed += screenPushed;
|
||||||
@ -947,6 +957,9 @@ namespace osu.Game
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset on screen change for sanity.
|
||||||
|
LocalUserPlaying.Value = false;
|
||||||
|
|
||||||
if (current is IOsuScreen currentOsuScreen)
|
if (current is IOsuScreen currentOsuScreen)
|
||||||
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
|
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The statistics that appear in the table, in order of appearance.
|
/// The statistics that appear in the table, in order of appearance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<HitResult> statisticResultTypes = new List<HitResult>();
|
private readonly List<(HitResult result, string displayName)> statisticResultTypes = new List<(HitResult, string)>();
|
||||||
|
|
||||||
private bool showPerformancePoints;
|
private bool showPerformancePoints;
|
||||||
|
|
||||||
@ -101,15 +101,24 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
};
|
};
|
||||||
|
|
||||||
// All statistics across all scores, unordered.
|
// All statistics across all scores, unordered.
|
||||||
var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet();
|
var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.Result)).ToHashSet();
|
||||||
|
|
||||||
|
var ruleset = scores.First().Ruleset.CreateInstance();
|
||||||
|
|
||||||
foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||||
{
|
{
|
||||||
if (!allScoreStatistics.Contains(result))
|
if (!allScoreStatistics.Contains(result))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
|
// for the time being ignore bonus result types.
|
||||||
statisticResultTypes.Add(result);
|
// this is not being sent from the API and will be empty in all cases.
|
||||||
|
if (result.IsBonus())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string displayName = ruleset.GetDisplayNameForHitResult(result);
|
||||||
|
|
||||||
|
columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
|
||||||
|
statisticResultTypes.Add((result, displayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showPerformancePoints)
|
if (showPerformancePoints)
|
||||||
@ -163,18 +172,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result);
|
var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result);
|
||||||
|
|
||||||
foreach (var result in statisticResultTypes)
|
foreach (var result in statisticResultTypes)
|
||||||
{
|
{
|
||||||
if (!availableStatistics.TryGetValue(result, out var stat))
|
if (!availableStatistics.TryGetValue(result.result, out var stat))
|
||||||
stat = (result, 0, null);
|
stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName);
|
||||||
|
|
||||||
content.Add(new OsuSpriteText
|
content.Add(new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}",
|
Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}",
|
||||||
Font = OsuFont.GetFont(size: text_size),
|
Font = OsuFont.GetFont(size: text_size),
|
||||||
Colour = stat.count == 0 ? Color4.Gray : Color4.White
|
Colour = stat.Count == 0 ? Color4.Gray : Color4.White
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -15,7 +14,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -117,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
|
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
|
||||||
ppColumn.Text = $@"{value.PP:N0}";
|
ppColumn.Text = $@"{value.PP:N0}";
|
||||||
|
|
||||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount));
|
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
||||||
modsColumn.Mods = value.Mods;
|
modsColumn.Mods = value.Mods;
|
||||||
|
|
||||||
if (scoreManager != null)
|
if (scoreManager != null)
|
||||||
@ -125,9 +123,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width)
|
private TextColumn createStatisticsColumn(HitResultDisplayStatistic stat) => new TextColumn(stat.DisplayName, smallFont, bottom_columns_min_width)
|
||||||
{
|
{
|
||||||
Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}"
|
Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}"
|
||||||
};
|
};
|
||||||
|
|
||||||
private class InfoColumn : CompositeDrawable
|
private class InfoColumn : CompositeDrawable
|
||||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
if (IsUserPaused) return;
|
if (IsUserPaused) return;
|
||||||
|
|
||||||
if (CurrentTrack.IsDummyDevice)
|
if (CurrentTrack.IsDummyDevice || beatmap.Value.BeatmapSetInfo.DeletePending)
|
||||||
{
|
{
|
||||||
if (beatmap.Disabled)
|
if (beatmap.Disabled)
|
||||||
return;
|
return;
|
||||||
|
@ -4,9 +4,11 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
|
|
||||||
@ -21,6 +23,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
|
|
||||||
private SettingsButton checkForUpdatesButton;
|
private SettingsButton checkForUpdatesButton;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private NotificationOverlay notifications { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(Storage storage, OsuConfigManager config, OsuGame game)
|
private void load(Storage storage, OsuConfigManager config, OsuGame game)
|
||||||
{
|
{
|
||||||
@ -38,7 +43,19 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
checkForUpdatesButton.Enabled.Value = false;
|
checkForUpdatesButton.Enabled.Value = false;
|
||||||
Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true));
|
Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!t.Result)
|
||||||
|
{
|
||||||
|
notifications?.Post(new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = $"You are running the latest release ({game.Version})",
|
||||||
|
Icon = FontAwesome.Solid.CheckCircle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForUpdatesButton.Enabled.Value = true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Input;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Input
|
namespace osu.Game.Overlays.Settings.Sections.Input
|
||||||
{
|
{
|
||||||
@ -47,10 +47,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
LabelText = "Map absolute input to window",
|
LabelText = "Map absolute input to window",
|
||||||
Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
|
Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
|
||||||
},
|
},
|
||||||
new SettingsEnumDropdown<ConfineMouseMode>
|
new SettingsEnumDropdown<OsuConfineMouseMode>
|
||||||
{
|
{
|
||||||
LabelText = "Confine mouse cursor to window",
|
LabelText = "Confine mouse cursor to window",
|
||||||
Current = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode),
|
Current = osuConfig.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode)
|
||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
|
@ -89,9 +89,23 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnDeselected() => Hide();
|
protected virtual void OnDeselected()
|
||||||
|
{
|
||||||
|
// selection blueprints are AlwaysPresent while the related DrawableHitObject is visible
|
||||||
|
// set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children.
|
||||||
|
foreach (var d in InternalChildren)
|
||||||
|
d.Hide();
|
||||||
|
|
||||||
protected virtual void OnSelected() => Show();
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnSelected()
|
||||||
|
{
|
||||||
|
foreach (var d in InternalChildren)
|
||||||
|
d.Show();
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
// When not selected, input is only required for the blueprint itself to receive IsHovering
|
// When not selected, input is only required for the blueprint itself to receive IsHovering
|
||||||
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
|
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
|
||||||
|
@ -38,7 +38,15 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
if (IncreaseFirstObjectVisibility.Value)
|
if (IncreaseFirstObjectVisibility.Value)
|
||||||
drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1);
|
{
|
||||||
|
drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h));
|
||||||
|
|
||||||
|
var firstObject = drawables.FirstOrDefault();
|
||||||
|
if (firstObject != null)
|
||||||
|
firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState;
|
||||||
|
|
||||||
|
drawables = drawables.Skip(1);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var dho in drawables)
|
foreach (var dho in drawables)
|
||||||
dho.ApplyCustomUpdateState += ApplyHiddenState;
|
dho.ApplyCustomUpdateState += ApplyHiddenState;
|
||||||
@ -65,6 +73,20 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The hit object to apply the state change to.</param>
|
||||||
|
/// <param name="state">The state of the hit object.</param>
|
||||||
|
protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a hidden state to the provided object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The hit object to apply the state change to.</param>
|
||||||
|
/// <param name="state">The state of the hit object.</param>
|
||||||
protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state)
|
protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
c.Changed += invalidate;
|
c.Changed += invalidate;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
case NotifyCollectionChangedAction.Remove:
|
case NotifyCollectionChangedAction.Remove:
|
||||||
foreach (var c in args.OldItems.Cast<PathControlPoint>())
|
foreach (var c in args.OldItems.Cast<PathControlPoint>())
|
||||||
c.Changed -= invalidate;
|
c.Changed -= invalidate;
|
||||||
|
@ -23,8 +23,10 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
{
|
{
|
||||||
@ -241,5 +243,52 @@ namespace osu.Game.Rulesets
|
|||||||
/// <returns>The <see cref="StatisticRow"/>s to display. Each <see cref="StatisticRow"/> may contain 0 or more <see cref="StatisticItem"/>.</returns>
|
/// <returns>The <see cref="StatisticRow"/>s to display. Each <see cref="StatisticRow"/> may contain 0 or more <see cref="StatisticItem"/>.</returns>
|
||||||
[NotNull]
|
[NotNull]
|
||||||
public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty<StatisticRow>();
|
public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty<StatisticRow>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all valid <see cref="HitResult"/>s for this ruleset.
|
||||||
|
/// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// All valid <see cref="HitResult"/>s along with a display-friendly name.
|
||||||
|
/// </returns>
|
||||||
|
public IEnumerable<(HitResult result, string displayName)> GetHitResults()
|
||||||
|
{
|
||||||
|
var validResults = GetValidHitResults();
|
||||||
|
|
||||||
|
// enumerate over ordered list to guarantee return order is stable.
|
||||||
|
foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
// hard blocked types, should never be displayed even if the ruleset tells us to.
|
||||||
|
case HitResult.None:
|
||||||
|
case HitResult.IgnoreHit:
|
||||||
|
case HitResult.IgnoreMiss:
|
||||||
|
// display is handled as a completion count with corresponding "hit" type.
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == HitResult.Miss || validResults.Contains(result))
|
||||||
|
yield return (result, GetDisplayNameForHitResult(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all valid <see cref="HitResult"/>s for this ruleset.
|
||||||
|
/// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="HitResult.Miss"/> is implicitly included. Special types like <see cref="HitResult.IgnoreHit"/> are ignored even when specified.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual IEnumerable<HitResult> GetValidHitResults() => OrderAttributeUtils.GetValuesInOrder<HitResult>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a display friendly name for the specified result type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The result type to get the name for.</param>
|
||||||
|
/// <returns>The display name.</returns>
|
||||||
|
public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
osu.Game/Scoring/HitResultDisplayStatistic.cs
Normal file
41
osu.Game/Scoring/HitResultDisplayStatistic.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Scoring
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compiled result data for a specific <see cref="HitResult"/> in a score.
|
||||||
|
/// </summary>
|
||||||
|
public class HitResultDisplayStatistic
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The associated result type.
|
||||||
|
/// </summary>
|
||||||
|
public HitResult Result { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The count of successful hits of this type.
|
||||||
|
/// </summary>
|
||||||
|
public int Count { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum achievable hits of this type. May be null if undetermined.
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxCount { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A custom display name for the result type. May be provided by rulesets to give better clarity.
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
public HitResultDisplayStatistic(HitResult result, int count, int? maxCount, string displayName)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
Count = count;
|
||||||
|
MaxCount = maxCount;
|
||||||
|
DisplayName = displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -213,22 +213,19 @@ namespace osu.Game.Scoring
|
|||||||
set => isLegacyScore = value;
|
set => isLegacyScore = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay()
|
public IEnumerable<HitResultDisplayStatistic> GetStatisticsForDisplay()
|
||||||
{
|
{
|
||||||
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
foreach (var r in Ruleset.CreateInstance().GetHitResults())
|
||||||
{
|
{
|
||||||
if (key.IsBonus())
|
int value = Statistics.GetOrDefault(r.result);
|
||||||
continue;
|
|
||||||
|
|
||||||
int value = Statistics.GetOrDefault(key);
|
switch (r.result)
|
||||||
|
|
||||||
switch (key)
|
|
||||||
{
|
{
|
||||||
case HitResult.SmallTickHit:
|
case HitResult.SmallTickHit:
|
||||||
{
|
{
|
||||||
int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||||
if (total > 0)
|
if (total > 0)
|
||||||
yield return (key, value, total);
|
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -237,7 +234,7 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss);
|
int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss);
|
||||||
if (total > 0)
|
if (total > 0)
|
||||||
yield return (key, value, total);
|
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -247,8 +244,7 @@ namespace osu.Game.Scoring
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (value > 0 || key == HitResult.Miss)
|
yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName);
|
||||||
yield return (key, value, null);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
|
||||||
{
|
{
|
||||||
@ -12,16 +12,23 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PointVisualisation : Box
|
public class PointVisualisation : Box
|
||||||
{
|
{
|
||||||
|
public const float WIDTH = 1;
|
||||||
|
|
||||||
public PointVisualisation(double startTime)
|
public PointVisualisation(double startTime)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
X = (float)startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointVisualisation()
|
||||||
{
|
{
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
Width = 1;
|
|
||||||
EdgeSmoothness = new Vector2(1, 0);
|
|
||||||
|
|
||||||
RelativePositionAxes = Axes.X;
|
RelativePositionAxes = Axes.X;
|
||||||
X = (float)startTime;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Width = WIDTH;
|
||||||
|
EdgeSmoothness = new Vector2(WIDTH, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected override void AddBlueprintFor(HitObject hitObject)
|
protected override void AddBlueprintFor(HitObject hitObject)
|
||||||
{
|
{
|
||||||
refreshTool();
|
refreshTool();
|
||||||
|
|
||||||
base.AddBlueprintFor(hitObject);
|
base.AddBlueprintFor(hitObject);
|
||||||
|
|
||||||
|
// on successful placement, the new combo button should be reset as this is the most common user interaction.
|
||||||
|
if (Beatmap.SelectedHitObjects.Count == 0)
|
||||||
|
NewCombo.Value = TernaryState.False;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createPlacement()
|
private void createPlacement()
|
||||||
|
@ -17,10 +17,28 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
public Action<float> OnRotation;
|
public Action<float> OnRotation;
|
||||||
public Action<Vector2, Anchor> OnScale;
|
public Action<Vector2, Anchor> OnScale;
|
||||||
public Action<Direction> OnFlip;
|
public Action<Direction> OnFlip;
|
||||||
|
public Action OnReverse;
|
||||||
|
|
||||||
public Action OperationStarted;
|
public Action OperationStarted;
|
||||||
public Action OperationEnded;
|
public Action OperationEnded;
|
||||||
|
|
||||||
|
private bool canReverse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether pattern reversing support should be enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanReverse
|
||||||
|
{
|
||||||
|
get => canReverse;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (canReverse == value) return;
|
||||||
|
|
||||||
|
canReverse = value;
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool canRotate;
|
private bool canRotate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -125,6 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (CanScaleX && CanScaleY) addFullScaleComponents();
|
if (CanScaleX && CanScaleY) addFullScaleComponents();
|
||||||
if (CanScaleY) addYScaleComponents();
|
if (CanScaleY) addYScaleComponents();
|
||||||
if (CanRotate) addRotationComponents();
|
if (CanRotate) addRotationComponents();
|
||||||
|
if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern", () => OnReverse?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRotationComponents()
|
private void addRotationComponents()
|
||||||
|
@ -103,6 +103,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
OnRotation = angle => HandleRotation(angle),
|
OnRotation = angle => HandleRotation(angle),
|
||||||
OnScale = (amount, anchor) => HandleScale(amount, anchor),
|
OnScale = (amount, anchor) => HandleScale(amount, anchor),
|
||||||
OnFlip = direction => HandleFlip(direction),
|
OnFlip = direction => HandleFlip(direction),
|
||||||
|
OnReverse = () => HandleReverse(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -141,7 +142,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// Handles the selected <see cref="DrawableHitObject"/>s being rotated.
|
/// Handles the selected <see cref="DrawableHitObject"/>s being rotated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="angle">The delta angle to apply to the selection.</param>
|
/// <param name="angle">The delta angle to apply to the selection.</param>
|
||||||
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be moved.</returns>
|
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be rotated.</returns>
|
||||||
public virtual bool HandleRotation(float angle) => false;
|
public virtual bool HandleRotation(float angle) => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -149,16 +150,22 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scale">The delta scale to apply, in playfield local coordinates.</param>
|
/// <param name="scale">The delta scale to apply, in playfield local coordinates.</param>
|
||||||
/// <param name="anchor">The point of reference where the scale is originating from.</param>
|
/// <param name="anchor">The point of reference where the scale is originating from.</param>
|
||||||
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be moved.</returns>
|
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be scaled.</returns>
|
||||||
public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false;
|
public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handled the selected <see cref="DrawableHitObject"/>s being flipped.
|
/// Handles the selected <see cref="DrawableHitObject"/>s being flipped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="direction">The direction to flip</param>
|
/// <param name="direction">The direction to flip</param>
|
||||||
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be moved.</returns>
|
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be flipped.</returns>
|
||||||
public virtual bool HandleFlip(Direction direction) => false;
|
public virtual bool HandleFlip(Direction direction) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the selected <see cref="DrawableHitObject"/>s being reversed pattern-wise.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether any <see cref="DrawableHitObject"/>s could be reversed.</returns>
|
||||||
|
public virtual bool HandleReverse() => false;
|
||||||
|
|
||||||
public bool OnPressed(PlatformAction action)
|
public bool OnPressed(PlatformAction action)
|
||||||
{
|
{
|
||||||
switch (action.ActionMethod)
|
switch (action.ActionMethod)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -12,7 +14,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class TimelineTickDisplay : TimelinePart
|
public class TimelineTickDisplay : TimelinePart<PointVisualisation>
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; }
|
||||||
@ -31,15 +33,63 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Cached tickCache = new Cached();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
beatDivisor.BindValueChanged(_ => createLines(), true);
|
beatDivisor.BindValueChanged(_ => tickCache.Invalidate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createLines()
|
/// <summary>
|
||||||
|
/// The visible time/position range of the timeline.
|
||||||
|
/// </summary>
|
||||||
|
private (float min, float max) visibleRange = (float.MinValue, float.MaxValue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next time/position value to the left of the display when tick regeneration needs to be run.
|
||||||
|
/// </summary>
|
||||||
|
private float? nextMinTick;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next time/position value to the right of the display when tick regeneration needs to be run.
|
||||||
|
/// </summary>
|
||||||
|
private float? nextMaxTick;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Timeline timeline { get; set; }
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
{
|
{
|
||||||
Clear();
|
base.Update();
|
||||||
|
|
||||||
|
if (timeline != null)
|
||||||
|
{
|
||||||
|
var newRange = (
|
||||||
|
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X,
|
||||||
|
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X);
|
||||||
|
|
||||||
|
if (visibleRange != newRange)
|
||||||
|
{
|
||||||
|
visibleRange = newRange;
|
||||||
|
|
||||||
|
// actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries.
|
||||||
|
if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick))
|
||||||
|
tickCache.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tickCache.IsValid)
|
||||||
|
createTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTicks()
|
||||||
|
{
|
||||||
|
int drawableIndex = 0;
|
||||||
|
int highestDivisor = BindableBeatDivisor.VALID_DIVISORS.Last();
|
||||||
|
|
||||||
|
nextMinTick = null;
|
||||||
|
nextMaxTick = null;
|
||||||
|
|
||||||
for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++)
|
for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++)
|
||||||
{
|
{
|
||||||
@ -50,41 +100,70 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value)
|
for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value)
|
||||||
{
|
{
|
||||||
var indexInBeat = beat % beatDivisor.Value;
|
float xPos = (float)t;
|
||||||
|
|
||||||
if (indexInBeat == 0)
|
if (t < visibleRange.min)
|
||||||
{
|
nextMinTick = xPos;
|
||||||
Add(new PointVisualisation(t)
|
else if (t > visibleRange.max)
|
||||||
{
|
nextMaxTick ??= xPos;
|
||||||
Colour = BindableBeatDivisor.GetColourFor(1, colours),
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// if this is the first beat in the beatmap, there is no next min tick
|
||||||
|
if (beat == 0 && i == 0)
|
||||||
|
nextMinTick = float.MinValue;
|
||||||
|
|
||||||
|
var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value);
|
||||||
|
|
||||||
var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value);
|
var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value);
|
||||||
var colour = BindableBeatDivisor.GetColourFor(divisor, colours);
|
var colour = BindableBeatDivisor.GetColourFor(divisor, colours);
|
||||||
var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f;
|
|
||||||
|
|
||||||
Add(new PointVisualisation(t)
|
// even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn.
|
||||||
{
|
var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f;
|
||||||
Colour = colour,
|
|
||||||
Height = height,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
});
|
|
||||||
|
|
||||||
Add(new PointVisualisation(t)
|
var topPoint = getNextUsablePoint();
|
||||||
{
|
topPoint.X = xPos;
|
||||||
Colour = colour,
|
topPoint.Colour = colour;
|
||||||
Anchor = Anchor.BottomLeft,
|
topPoint.Height = height;
|
||||||
Origin = Anchor.BottomCentre,
|
topPoint.Anchor = Anchor.TopLeft;
|
||||||
Height = height,
|
topPoint.Origin = Anchor.TopCentre;
|
||||||
});
|
|
||||||
|
var bottomPoint = getNextUsablePoint();
|
||||||
|
bottomPoint.X = xPos;
|
||||||
|
bottomPoint.Colour = colour;
|
||||||
|
bottomPoint.Anchor = Anchor.BottomLeft;
|
||||||
|
bottomPoint.Origin = Anchor.BottomCentre;
|
||||||
|
bottomPoint.Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
beat++;
|
beat++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int usedDrawables = drawableIndex;
|
||||||
|
|
||||||
|
// save a few drawables beyond the currently used for edge cases.
|
||||||
|
while (drawableIndex < Math.Min(usedDrawables + 16, Count))
|
||||||
|
Children[drawableIndex++].Hide();
|
||||||
|
|
||||||
|
// expire any excess
|
||||||
|
while (drawableIndex < Count)
|
||||||
|
Children[drawableIndex++].Expire();
|
||||||
|
|
||||||
|
tickCache.Validate();
|
||||||
|
|
||||||
|
Drawable getNextUsablePoint()
|
||||||
|
{
|
||||||
|
PointVisualisation point;
|
||||||
|
if (drawableIndex >= Count)
|
||||||
|
Add(point = new PointVisualisation());
|
||||||
|
else
|
||||||
|
point = Children[drawableIndex];
|
||||||
|
|
||||||
|
drawableIndex++;
|
||||||
|
point.Show();
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,10 +469,17 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private void confirmExit()
|
private void confirmExit()
|
||||||
{
|
{
|
||||||
|
// stop the track if playing to allow the parent screen to choose a suitable playback mode.
|
||||||
|
Beatmap.Value.Track.Stop();
|
||||||
|
|
||||||
if (isNewBeatmap)
|
if (isNewBeatmap)
|
||||||
{
|
{
|
||||||
// confirming exit without save means we should delete the new beatmap completely.
|
// confirming exit without save means we should delete the new beatmap completely.
|
||||||
beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet);
|
beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet);
|
||||||
|
|
||||||
|
// in theory this shouldn't be required but due to EF core not sharing instance states 100%
|
||||||
|
// MusicController is unaware of the changed DeletePending state.
|
||||||
|
Beatmap.SetDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
exitConfirmed = true;
|
exitConfirmed = true;
|
||||||
|
@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||||
|
|
||||||
|
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
|
||||||
|
|
||||||
public int RestartCount;
|
public int RestartCount;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -155,8 +157,8 @@ namespace osu.Game.Screens.Play
|
|||||||
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
|
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, OsuConfigManager config)
|
private void load(AudioManager audio, OsuConfigManager config, OsuGame game)
|
||||||
{
|
{
|
||||||
Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray();
|
Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray();
|
||||||
|
|
||||||
@ -172,6 +174,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||||
|
|
||||||
|
if (game != null)
|
||||||
|
LocalUserPlaying.BindTo(game.LocalUserPlaying);
|
||||||
|
|
||||||
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
|
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
|
||||||
|
|
||||||
ScoreProcessor = ruleset.CreateScoreProcessor();
|
ScoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
@ -219,9 +224,9 @@ namespace osu.Game.Screens.Play
|
|||||||
skipOverlay.Hide();
|
skipOverlay.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode());
|
DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
|
||||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode());
|
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
|
||||||
breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode());
|
breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState());
|
||||||
|
|
||||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
|
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
|
||||||
|
|
||||||
@ -353,14 +358,11 @@ namespace osu.Game.Screens.Play
|
|||||||
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
|
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOverlayActivationMode()
|
private void updateGameplayState()
|
||||||
{
|
{
|
||||||
bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value;
|
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
|
||||||
|
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
|
||||||
if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays)
|
LocalUserPlaying.Value = inGameplay;
|
||||||
OverlayActivationMode.Value = OverlayActivation.UserTriggered;
|
|
||||||
else
|
|
||||||
OverlayActivationMode.Value = OverlayActivation.Disabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePauseOnFocusLostState() =>
|
private void updatePauseOnFocusLostState() =>
|
||||||
@ -661,7 +663,7 @@ namespace osu.Game.Screens.Play
|
|||||||
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(musicController.CurrentTrack);
|
mod.ApplyToTrack(musicController.CurrentTrack);
|
||||||
|
|
||||||
updateOverlayActivationMode();
|
updateGameplayState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(IScreen next)
|
public override void OnSuspending(IScreen next)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
@ -117,7 +116,7 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 5),
|
Spacing = new Vector2(0, 5),
|
||||||
ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount))
|
ChildrenEnumerable = score.GetStatisticsForDisplay().Where(s => !s.Result.IsBonus()).Select(createStatistic)
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -199,8 +198,8 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createStatistic(HitResult result, int count, int? maxCount)
|
private Drawable createStatistic(HitResultDisplayStatistic result)
|
||||||
=> createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}");
|
=> createStatistic(result.DisplayName, result.MaxCount == null ? $"{result.Count}" : $"{result.Count}/{result.MaxCount}");
|
||||||
|
|
||||||
private Drawable createStatistic(string key, string value) => new Container
|
private Drawable createStatistic(string key, string value) => new Container
|
||||||
{
|
{
|
||||||
|
@ -64,155 +64,168 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
new CounterStatistic("pp", (int)(score.PP ?? 0)),
|
new CounterStatistic("pp", (int)(score.PP ?? 0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var bottomStatistics = new List<StatisticDisplay>();
|
var bottomStatistics = new List<HitResultStatistic>();
|
||||||
|
|
||||||
foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay())
|
foreach (var result in score.GetStatisticsForDisplay())
|
||||||
bottomStatistics.Add(new HitResultStatistic(key, value, maxCount));
|
bottomStatistics.Add(new HitResultStatistic(result));
|
||||||
|
|
||||||
statisticDisplays.AddRange(topStatistics);
|
statisticDisplays.AddRange(topStatistics);
|
||||||
statisticDisplays.AddRange(bottomStatistics);
|
statisticDisplays.AddRange(bottomStatistics);
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new FillFlowContainer
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(20),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
new FillFlowContainer
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
new OsuSpriteText
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
|
|
||||||
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
|
||||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
|
||||||
Truncate = true,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
|
|
||||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
|
||||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
|
||||||
Truncate = true,
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Margin = new MarginPadding { Top = 40 },
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 230,
|
|
||||||
Child = new AccuracyCircle(score)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
|
||||||
FillMode = FillMode.Fit,
|
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
||||||
}
|
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||||
},
|
Truncate = true,
|
||||||
scoreCounter = new TotalScoreCounter
|
},
|
||||||
{
|
new OsuSpriteText
|
||||||
Margin = new MarginPadding { Top = 0, Bottom = 5 },
|
|
||||||
Current = { Value = 0 },
|
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true
|
|
||||||
},
|
|
||||||
starAndModDisplay = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Spacing = new Vector2(5, 0),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new StarRatingDisplay(beatmap)
|
Anchor = Anchor.TopCentre,
|
||||||
{
|
Origin = Anchor.TopCentre,
|
||||||
Anchor = Anchor.CentreLeft,
|
Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
|
||||||
Origin = Anchor.CentreLeft
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||||
},
|
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||||
}
|
Truncate = true,
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new Container
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Margin = new MarginPadding { Top = 40 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 230,
|
||||||
|
Child = new AccuracyCircle(score)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.Centre,
|
||||||
Text = beatmap.Version,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
FillMode = FillMode.Fit,
|
||||||
},
|
}
|
||||||
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
|
},
|
||||||
|
scoreCounter = new TotalScoreCounter
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 0, Bottom = 5 },
|
||||||
|
Current = { Value = 0 },
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
},
|
||||||
|
starAndModDisplay = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Spacing = new Vector2(5, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
new StarRatingDisplay(beatmap)
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
}.With(t =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(creator))
|
|
||||||
{
|
{
|
||||||
t.AddText("mapped by ");
|
Anchor = Anchor.CentreLeft,
|
||||||
t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
|
Origin = Anchor.CentreLeft
|
||||||
}
|
},
|
||||||
})
|
}
|
||||||
}
|
},
|
||||||
},
|
new FillFlowContainer
|
||||||
}
|
{
|
||||||
},
|
Anchor = Anchor.TopCentre,
|
||||||
new FillFlowContainer
|
Origin = Anchor.TopCentre,
|
||||||
{
|
Direction = FillDirection.Vertical,
|
||||||
RelativeSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.Both,
|
||||||
AutoSizeAxes = Axes.Y,
|
Children = new Drawable[]
|
||||||
Direction = FillDirection.Vertical,
|
{
|
||||||
Spacing = new Vector2(0, 5),
|
new OsuSpriteText
|
||||||
Children = new Drawable[]
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = beatmap.Version,
|
||||||
|
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
}.With(t =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(creator))
|
||||||
|
{
|
||||||
|
t.AddText("mapped by ");
|
||||||
|
t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
new GridContainer
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new GridContainer
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Content = new[] { topStatistics.Cast<Drawable>().ToArray() },
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
RelativeSizeAxes = Axes.X,
|
||||||
}
|
AutoSizeAxes = Axes.Y,
|
||||||
},
|
Content = new[] { topStatistics.Cast<Drawable>().ToArray() },
|
||||||
new GridContainer
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
AutoSizeAxes = Axes.Y,
|
}
|
||||||
Content = new[] { bottomStatistics.Cast<Drawable>().ToArray() },
|
},
|
||||||
RowDimensions = new[]
|
new GridContainer
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() },
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() },
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
|
|
||||||
Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
|
||||||
|
Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,26 +2,26 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||||
{
|
{
|
||||||
public class HitResultStatistic : CounterStatistic
|
public class HitResultStatistic : CounterStatistic
|
||||||
{
|
{
|
||||||
private readonly HitResult result;
|
public readonly HitResult Result;
|
||||||
|
|
||||||
public HitResultStatistic(HitResult result, int count, int? maxCount = null)
|
public HitResultStatistic(HitResultDisplayStatistic result)
|
||||||
: base(result.GetDescription(), count, maxCount)
|
: base(result.DisplayName, result.Count, result.MaxCount)
|
||||||
{
|
{
|
||||||
this.result = result;
|
Result = result.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
HeaderText.Colour = colours.ForHitResult(result);
|
HeaderText.Colour = colours.ForHitResult(Result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height of the panel when contracted.
|
/// Height of the panel when contracted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float contracted_height = 355;
|
private const float contracted_height = 385;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the panel when expanded.
|
/// Width of the panel when expanded.
|
||||||
@ -39,7 +39,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height of the panel when expanded.
|
/// Height of the panel when expanded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float expanded_height = 560;
|
private const float expanded_height = 586;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height of the top layer when the panel is expanded.
|
/// Height of the top layer when the panel is expanded.
|
||||||
@ -105,11 +105,16 @@ namespace osu.Game.Screens.Ranking
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
// ScorePanel doesn't include the top extruding area in its own size.
|
||||||
|
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
|
||||||
|
const float vertical_fudge = 20;
|
||||||
|
|
||||||
InternalChild = content = new Container
|
InternalChild = content = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(40),
|
Size = new Vector2(40),
|
||||||
|
Y = vertical_fudge,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
topLayerContainer = new Container
|
topLayerContainer = new Container
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
foreach (var e in hitEvents)
|
foreach (var e in hitEvents)
|
||||||
{
|
{
|
||||||
int binOffset = (int)(e.TimeOffset / binSize);
|
int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero);
|
||||||
bins[timing_distribution_centre_bin_index + binOffset]++;
|
bins[timing_distribution_centre_bin_index + binOffset]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Updater
|
|||||||
version = game.Version;
|
version = game.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task PerformUpdateCheck()
|
protected override async Task<bool> PerformUpdateCheck()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -53,12 +53,17 @@ namespace osu.Game.Updater
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// we shouldn't crash on a web failure. or any failure for the matter.
|
// we shouldn't crash on a web failure. or any failure for the matter.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getBestUrl(GitHubRelease release)
|
private string getBestUrl(GitHubRelease release)
|
||||||
|
@ -57,25 +57,31 @@ namespace osu.Game.Updater
|
|||||||
|
|
||||||
private readonly object updateTaskLock = new object();
|
private readonly object updateTaskLock = new object();
|
||||||
|
|
||||||
private Task updateCheckTask;
|
private Task<bool> updateCheckTask;
|
||||||
|
|
||||||
public async Task CheckForUpdateAsync()
|
public async Task<bool> CheckForUpdateAsync()
|
||||||
{
|
{
|
||||||
if (!CanCheckForUpdate)
|
if (!CanCheckForUpdate)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
Task waitTask;
|
Task<bool> waitTask;
|
||||||
|
|
||||||
lock (updateTaskLock)
|
lock (updateTaskLock)
|
||||||
waitTask = (updateCheckTask ??= PerformUpdateCheck());
|
waitTask = (updateCheckTask ??= PerformUpdateCheck());
|
||||||
|
|
||||||
await waitTask;
|
bool hasUpdates = await waitTask;
|
||||||
|
|
||||||
lock (updateTaskLock)
|
lock (updateTaskLock)
|
||||||
updateCheckTask = null;
|
updateCheckTask = null;
|
||||||
|
|
||||||
|
return hasUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task PerformUpdateCheck() => Task.CompletedTask;
|
/// <summary>
|
||||||
|
/// Performs an asynchronous check for application updates.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether any update is waiting. May return true if an error occured (there is potentially an update available).</returns>
|
||||||
|
protected virtual Task<bool> PerformUpdateCheck() => Task.FromResult(false);
|
||||||
|
|
||||||
private class UpdateCompleteNotification : SimpleNotification
|
private class UpdateCompleteNotification : SimpleNotification
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.1004.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1009.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.6" />
|
<PackageReference Include="Sentry" Version="2.1.6" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1004.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1009.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
@ -80,7 +80,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.1004.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1009.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user