mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into fix-multiplayer-unobserved
This commit is contained in:
@ -1,19 +1,16 @@
|
||||
// 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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
internal class TimingSection : Section<TimingControlPoint>
|
||||
{
|
||||
private SettingsSlider<double> bpmSlider;
|
||||
private LabelledTimeSignature timeSignature;
|
||||
private BPMTextBox bpmTextEntry;
|
||||
|
||||
@ -23,7 +20,6 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Flow.AddRange(new Drawable[]
|
||||
{
|
||||
bpmTextEntry = new BPMTextBox(),
|
||||
bpmSlider = new BPMSlider(),
|
||||
timeSignature = new LabelledTimeSignature
|
||||
{
|
||||
Label = "Time Signature"
|
||||
@ -35,11 +31,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
if (point.NewValue != null)
|
||||
{
|
||||
bpmSlider.Current = point.NewValue.BeatLengthBindable;
|
||||
bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
|
||||
bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
|
||||
// no need to hook change handler here as it's the same bindable as above
|
||||
bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
|
||||
timeSignature.Current = point.NewValue.TimeSignatureBindable;
|
||||
timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
||||
@ -102,51 +95,6 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
}
|
||||
|
||||
private class BPMSlider : SettingsSlider<double>
|
||||
{
|
||||
private const double sane_minimum = 60;
|
||||
private const double sane_maximum = 240;
|
||||
|
||||
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
|
||||
|
||||
private readonly BindableDouble bpmBindable = new BindableDouble(60000 / TimingControlPoint.DEFAULT_BEAT_LENGTH)
|
||||
{
|
||||
MinValue = sane_minimum,
|
||||
MaxValue = sane_maximum,
|
||||
};
|
||||
|
||||
public BPMSlider()
|
||||
{
|
||||
beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
|
||||
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
|
||||
|
||||
base.Current = bpmBindable;
|
||||
|
||||
TransferValueOnCommit = true;
|
||||
}
|
||||
|
||||
public override Bindable<double> Current
|
||||
{
|
||||
get => base.Current;
|
||||
set
|
||||
{
|
||||
// incoming will be beat length, not bpm
|
||||
beatLengthBindable.UnbindBindings();
|
||||
beatLengthBindable.BindTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrent(double newValue)
|
||||
{
|
||||
// we use a more sane range for the slider display unless overridden by the user.
|
||||
// if a value comes in outside our range, we should expand temporarily.
|
||||
bpmBindable.MinValue = Math.Min(newValue, sane_minimum);
|
||||
bpmBindable.MaxValue = Math.Max(newValue, sane_maximum);
|
||||
|
||||
bpmBindable.Value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static double beatLengthToBpm(double beatLength) => 60000 / beatLength;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
@ -27,6 +29,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
[CanBeNull]
|
||||
private MultiplayerRoom room => multiplayerClient.Room;
|
||||
|
||||
private Sample countdownTickSample;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
countdownTickSample = audio.Samples.Get(@"Multiplayer/countdown-tick");
|
||||
// disabled for now pending further work on sound effect
|
||||
// countdownTickFinalSample = audio.Samples.Get(@"Multiplayer/countdown-tick-final");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -36,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
|
||||
private MultiplayerCountdown countdown;
|
||||
private DateTimeOffset countdownChangeTime;
|
||||
private double countdownChangeTime;
|
||||
private ScheduledDelegate countdownUpdateDelegate;
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||
@ -44,20 +56,55 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
if (countdown != room?.Countdown)
|
||||
{
|
||||
countdown = room?.Countdown;
|
||||
countdownChangeTime = DateTimeOffset.Now;
|
||||
countdownChangeTime = Time.Current;
|
||||
}
|
||||
|
||||
scheduleNextCountdownUpdate();
|
||||
|
||||
updateButtonText();
|
||||
updateButtonColour();
|
||||
});
|
||||
|
||||
private void scheduleNextCountdownUpdate()
|
||||
{
|
||||
countdownUpdateDelegate?.Cancel();
|
||||
|
||||
if (countdown != null)
|
||||
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 100, true);
|
||||
{
|
||||
// The remaining time on a countdown may be at a fractional portion between two seconds.
|
||||
// We want to align certain audio/visual cues to the point at which integer seconds change.
|
||||
// To do so, we schedule to the next whole second. Note that scheduler invocation isn't
|
||||
// guaranteed to be accurate, so this may still occur slightly late, but even in such a case
|
||||
// the next invocation will be roughly correct.
|
||||
double timeToNextSecond = countdownTimeRemaining.TotalMilliseconds % 1000;
|
||||
|
||||
countdownUpdateDelegate = Scheduler.AddDelayed(onCountdownTick, timeToNextSecond);
|
||||
}
|
||||
else
|
||||
{
|
||||
countdownUpdateDelegate?.Cancel();
|
||||
countdownUpdateDelegate = null;
|
||||
}
|
||||
|
||||
updateButtonText();
|
||||
updateButtonColour();
|
||||
});
|
||||
void onCountdownTick()
|
||||
{
|
||||
updateButtonText();
|
||||
|
||||
int secondsRemaining = countdownTimeRemaining.Seconds;
|
||||
|
||||
playTickSound(secondsRemaining);
|
||||
|
||||
if (secondsRemaining > 0)
|
||||
scheduleNextCountdownUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void playTickSound(int secondsRemaining)
|
||||
{
|
||||
if (secondsRemaining < 10) countdownTickSample?.Play();
|
||||
// disabled for now pending further work on sound effect
|
||||
// if (secondsRemaining <= 3) countdownTickFinalSample?.Play();
|
||||
}
|
||||
|
||||
private void updateButtonText()
|
||||
{
|
||||
@ -75,15 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
if (countdown != null)
|
||||
{
|
||||
TimeSpan timeElapsed = DateTimeOffset.Now - countdownChangeTime;
|
||||
TimeSpan countdownRemaining;
|
||||
|
||||
if (timeElapsed > countdown.TimeRemaining)
|
||||
countdownRemaining = TimeSpan.Zero;
|
||||
else
|
||||
countdownRemaining = countdown.TimeRemaining - timeElapsed;
|
||||
|
||||
string countdownText = $"Starting in {countdownRemaining:mm\\:ss}";
|
||||
string countdownText = $"Starting in {countdownTimeRemaining:mm\\:ss}";
|
||||
|
||||
switch (localUser?.State)
|
||||
{
|
||||
@ -116,6 +155,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan countdownTimeRemaining
|
||||
{
|
||||
get
|
||||
{
|
||||
double timeElapsed = Time.Current - countdownChangeTime;
|
||||
TimeSpan remaining;
|
||||
|
||||
if (timeElapsed > countdown.TimeRemaining.TotalMilliseconds)
|
||||
remaining = TimeSpan.Zero;
|
||||
else
|
||||
remaining = countdown.TimeRemaining - TimeSpan.FromMilliseconds(timeElapsed);
|
||||
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtonColour()
|
||||
{
|
||||
if (room == null)
|
||||
|
@ -117,8 +117,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
{
|
||||
base.PlaylistItemChanged(item);
|
||||
|
||||
removeItemFromLists(item.ID);
|
||||
addItemToLists(item);
|
||||
var newApiItem = Playlist.SingleOrDefault(i => i.ID == item.ID);
|
||||
var existingApiItemInQueue = queueList.Items.SingleOrDefault(i => i.ID == item.ID);
|
||||
|
||||
// Test if the only change between the two playlist items is the order.
|
||||
if (newApiItem != null && existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem))
|
||||
{
|
||||
// Set the new playlist order directly without refreshing the DrawablePlaylistItem.
|
||||
existingApiItemInQueue.PlaylistOrder = newApiItem.PlaylistOrder;
|
||||
|
||||
// The following isn't really required, but is here for safety and explicitness.
|
||||
// MultiplayerQueueList internally binds to changes in Playlist to invalidate its own layout, which is mutated on every playlist operation.
|
||||
queueList.Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
removeItemFromLists(item.ID);
|
||||
addItemToLists(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void addItemToLists(MultiplayerPlaylistItem item)
|
||||
|
@ -9,6 +9,7 @@ using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Skinning;
|
||||
@ -84,9 +85,17 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// <returns>The new instance.</returns>
|
||||
public Drawable CreateInstance()
|
||||
{
|
||||
Drawable d = (Drawable)Activator.CreateInstance(Type);
|
||||
d.ApplySkinnableInfo(this);
|
||||
return d;
|
||||
try
|
||||
{
|
||||
Drawable d = (Drawable)Activator.CreateInstance(Type);
|
||||
d.ApplySkinnableInfo(this);
|
||||
return d;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Unable to create skin component {Type.Name}");
|
||||
return Drawable.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select
|
||||
HeaderText = @"Confirm deletion of";
|
||||
Buttons = new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogOkButton
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = @"Yes. Totally. Delete it.",
|
||||
Action = () => manager?.Delete(beatmap),
|
||||
|
@ -174,7 +174,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Action == Hotkey)
|
||||
if (e.Action == Hotkey && !e.Repeat)
|
||||
{
|
||||
TriggerClick();
|
||||
return true;
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select
|
||||
HeaderText = "Confirm deletion of local score";
|
||||
Buttons = new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogOkButton
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = "Yes. Please.",
|
||||
Action = () => scoreManager?.Delete(score)
|
||||
|
Reference in New Issue
Block a user