2021-04-29 16:20:22 +08:00
|
|
|
// 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.
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
2022-03-11 22:08:40 +08:00
|
|
|
using System.Diagnostics;
|
2023-11-24 16:19:03 +08:00
|
|
|
using System.Linq;
|
2022-03-15 17:10:30 +08:00
|
|
|
using osu.Framework.Allocation;
|
2023-06-20 18:18:17 +08:00
|
|
|
using osu.Framework.Bindables;
|
2021-04-29 16:20:22 +08:00
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
2022-03-02 19:04:53 +08:00
|
|
|
using osu.Framework.Graphics.Primitives;
|
2024-02-19 16:45:03 +08:00
|
|
|
using osu.Framework.Input;
|
2021-04-29 16:20:22 +08:00
|
|
|
using osu.Framework.Input.Bindings;
|
2021-09-16 17:26:12 +08:00
|
|
|
using osu.Framework.Input.Events;
|
2023-11-24 16:19:03 +08:00
|
|
|
using osu.Framework.Screens;
|
2024-02-19 16:45:03 +08:00
|
|
|
using osu.Framework.Testing;
|
2023-11-24 16:19:03 +08:00
|
|
|
using osu.Game.Beatmaps;
|
2023-06-20 18:18:17 +08:00
|
|
|
using osu.Game.Configuration;
|
2021-04-29 16:20:22 +08:00
|
|
|
using osu.Game.Graphics.Containers;
|
|
|
|
using osu.Game.Input.Bindings;
|
Forcibly change ruleset to correct one before entering gameplay from main menu
Closes #25663 (again).
As it turns out, in some scenarios it can be the case that the current
game-global `Beatmap` is not valid for the current game-global
`Ruleset`. The validity of one and the other in conjunction is only
really validated by the song select screen; elsewhere there is no
guarantee that the global beatmap is playable using the global ruleset.
However, this only comes up in very specific circumstances, namely one:
when trying to autoplay a catch beatmap with osu! ruleset globally
active via the skin editor flow.
`Player` is responsible for retrieving the beatmap to be played. It does
so by invoking the appropriate beatmap converter and asking it if the
beatmap can be converted:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Beatmaps/WorkingBeatmap.cs#L262-L266
If the code above throws, `Player` actually silently covers for this, by
trying the beatmap's default ruleset instead:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Screens/Play/Player.cs#L529-L536
However, for the pairing of osu! ruleset and catch beatmap, this fails,
as `OsuBeatmapConverter`'s condition necessary for permitting conversion
is that the objects have a defined position:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs#L25
which they will do, due to the fact that all catch beatmaps are really
just osu! beatmaps but with conversion steps applied, and thus `Player`
succeeds to load the catch beatmap in osu! ruleset.
In the skin editor scenario, this would lead to the secondary failure
of the skin editor trying to apply `CatchModAutoplay` on top of all
of that, which would fail at the hard-cast of the beatmap
to `CatchBeatmap`.
2023-12-06 17:12:25 +08:00
|
|
|
using osu.Game.Rulesets;
|
2023-11-24 16:19:03 +08:00
|
|
|
using osu.Game.Rulesets.Mods;
|
|
|
|
using osu.Game.Scoring;
|
2022-03-15 17:10:30 +08:00
|
|
|
using osu.Game.Screens;
|
2023-02-15 18:35:37 +08:00
|
|
|
using osu.Game.Screens.Edit;
|
2022-06-06 17:02:42 +08:00
|
|
|
using osu.Game.Screens.Edit.Components;
|
2023-11-24 16:19:03 +08:00
|
|
|
using osu.Game.Screens.Menu;
|
|
|
|
using osu.Game.Screens.Play;
|
|
|
|
using osu.Game.Screens.Select;
|
2023-12-04 21:14:07 +08:00
|
|
|
using osu.Game.Users;
|
2023-11-24 16:19:03 +08:00
|
|
|
using osu.Game.Utils;
|
2022-06-06 17:27:41 +08:00
|
|
|
using osuTK;
|
2021-04-29 16:20:22 +08:00
|
|
|
|
2023-01-26 17:21:04 +08:00
|
|
|
namespace osu.Game.Overlays.SkinEditor
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
|
|
|
/// <summary>
|
2021-05-03 13:58:23 +08:00
|
|
|
/// A container which handles loading a skin editor on user request for a specified target.
|
|
|
|
/// This also handles the scaling / positioning adjustment of the target.
|
2021-04-29 16:20:22 +08:00
|
|
|
/// </summary>
|
2022-11-24 13:32:20 +08:00
|
|
|
public partial class SkinEditorOverlay : OverlayContainer, IKeyBindingHandler<GlobalAction>
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2022-03-15 17:00:32 +08:00
|
|
|
private readonly ScalingContainer scalingContainer;
|
2021-09-29 17:50:55 +08:00
|
|
|
|
2022-03-21 15:52:06 +08:00
|
|
|
protected override bool BlockNonPositionalInput => true;
|
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
private SkinEditor? skinEditor;
|
2021-04-29 16:20:22 +08:00
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
[Resolved]
|
|
|
|
private IPerformFromScreenRunner? performer { get; set; }
|
|
|
|
|
2023-02-15 18:35:37 +08:00
|
|
|
[Cached]
|
|
|
|
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
[Resolved]
|
|
|
|
private OsuGame game { get; set; } = null!;
|
2022-03-15 17:10:30 +08:00
|
|
|
|
2023-11-24 16:34:08 +08:00
|
|
|
[Resolved]
|
|
|
|
private MusicController music { get; set; } = null!;
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
[Resolved]
|
|
|
|
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
|
|
|
|
Forcibly change ruleset to correct one before entering gameplay from main menu
Closes #25663 (again).
As it turns out, in some scenarios it can be the case that the current
game-global `Beatmap` is not valid for the current game-global
`Ruleset`. The validity of one and the other in conjunction is only
really validated by the song select screen; elsewhere there is no
guarantee that the global beatmap is playable using the global ruleset.
However, this only comes up in very specific circumstances, namely one:
when trying to autoplay a catch beatmap with osu! ruleset globally
active via the skin editor flow.
`Player` is responsible for retrieving the beatmap to be played. It does
so by invoking the appropriate beatmap converter and asking it if the
beatmap can be converted:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Beatmaps/WorkingBeatmap.cs#L262-L266
If the code above throws, `Player` actually silently covers for this, by
trying the beatmap's default ruleset instead:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Screens/Play/Player.cs#L529-L536
However, for the pairing of osu! ruleset and catch beatmap, this fails,
as `OsuBeatmapConverter`'s condition necessary for permitting conversion
is that the objects have a defined position:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs#L25
which they will do, due to the fact that all catch beatmaps are really
just osu! beatmaps but with conversion steps applied, and thus `Player`
succeeds to load the catch beatmap in osu! ruleset.
In the skin editor scenario, this would lead to the secondary failure
of the skin editor trying to apply `CatchModAutoplay` on top of all
of that, which would fail at the hard-cast of the beatmap
to `CatchBeatmap`.
2023-12-06 17:12:25 +08:00
|
|
|
[Resolved]
|
|
|
|
private Bindable<RulesetInfo> ruleset { get; set; } = null!;
|
|
|
|
|
2023-11-24 16:34:08 +08:00
|
|
|
[Resolved]
|
|
|
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
private OsuScreen? lastTargetScreen;
|
2024-02-19 16:45:03 +08:00
|
|
|
private InvokeOnDisposal? nestedInputManagerDisable;
|
2022-03-15 17:00:32 +08:00
|
|
|
|
2022-06-06 17:27:41 +08:00
|
|
|
private Vector2 lastDrawSize;
|
|
|
|
|
2022-03-15 17:00:32 +08:00
|
|
|
public SkinEditorOverlay(ScalingContainer scalingContainer)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2022-03-15 17:00:32 +08:00
|
|
|
this.scalingContainer = scalingContainer;
|
2021-04-29 16:20:22 +08:00
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
|
|
}
|
|
|
|
|
2023-06-20 18:18:17 +08:00
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
private void load(OsuConfigManager config)
|
|
|
|
{
|
|
|
|
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
|
|
|
|
}
|
|
|
|
|
2021-09-16 17:26:12 +08:00
|
|
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2021-09-16 17:26:12 +08:00
|
|
|
switch (e.Action)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2021-04-29 16:40:58 +08:00
|
|
|
case GlobalAction.Back:
|
2021-07-22 14:59:00 +08:00
|
|
|
if (skinEditor?.State.Value != Visibility.Visible)
|
|
|
|
break;
|
|
|
|
|
|
|
|
Hide();
|
2021-07-20 18:36:12 +08:00
|
|
|
return true;
|
2021-04-29 16:20:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-03-21 15:52:06 +08:00
|
|
|
protected override void PopIn()
|
2021-07-20 18:36:12 +08:00
|
|
|
{
|
2023-06-21 15:11:19 +08:00
|
|
|
globallyDisableBeatmapSkinSetting();
|
|
|
|
|
2021-12-30 15:03:16 +08:00
|
|
|
if (skinEditor != null)
|
2021-07-20 18:36:12 +08:00
|
|
|
{
|
2024-02-19 16:45:03 +08:00
|
|
|
disableNestedInputManagers();
|
2021-12-30 15:03:16 +08:00
|
|
|
skinEditor.Show();
|
2023-12-28 06:45:56 +08:00
|
|
|
|
|
|
|
if (lastTargetScreen is MainMenu)
|
|
|
|
PresentGameplay();
|
|
|
|
|
2021-12-30 15:03:16 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-15 17:00:32 +08:00
|
|
|
var editor = new SkinEditor();
|
2022-03-21 22:54:47 +08:00
|
|
|
|
2022-06-24 20:25:23 +08:00
|
|
|
editor.State.BindValueChanged(_ => updateComponentVisibility());
|
2021-09-29 17:50:55 +08:00
|
|
|
|
2021-12-30 15:03:16 +08:00
|
|
|
skinEditor = editor;
|
2021-12-30 15:02:08 +08:00
|
|
|
|
2022-03-21 22:54:47 +08:00
|
|
|
LoadComponentAsync(editor, _ =>
|
2021-12-30 15:03:16 +08:00
|
|
|
{
|
|
|
|
if (editor != skinEditor)
|
|
|
|
return;
|
|
|
|
|
2022-03-21 22:54:47 +08:00
|
|
|
AddInternal(editor);
|
2022-03-15 17:00:32 +08:00
|
|
|
|
Fix `SkinEditorOverlay` freezing when `ReplayPlayer` screen exits early
Originally when popping in, the ReplayPlayer was loaded first (if previous screen was MainMenu), and afterwards the SkinEditor component was loaded asynchronously. However, if the ReplayPlayer screen exits quickly (like in the event the beatmap has no objects), the skin editor component has not finished initializing (this is before it was even added to the component tree, so it's still not marked `Visible`), then the screen exiting will cause `OsuGame` to call SetTarget(newScreen) -> setTarget(...) which sees that the cached `skinEditor` is not visible yet, and hides/nulls the field. This is the point where LoadComponentAsync(editor, ...) finishes, and the callback sees that the cached skinEditor field is now different (null) than the one that was loaded, and never adds it to the component tree. This occurrence is unhandled and as such the SkinEditorOverlay never hides itself, consuming all input infinitely.
This PR changes the loading to start loading the ReplayPlayer *after* the SkinEditor has been loaded and added to the component tree.
Additionally, this lowers the exit delay for ReplayPlayer and changes the "no hit objects" notification to not be an error since it's a controlled exit.
2023-12-27 03:08:21 +08:00
|
|
|
if (lastTargetScreen is MainMenu)
|
|
|
|
PresentGameplay();
|
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
Debug.Assert(lastTargetScreen != null);
|
|
|
|
|
2022-03-21 22:54:47 +08:00
|
|
|
SetTarget(lastTargetScreen);
|
2021-12-30 15:03:16 +08:00
|
|
|
});
|
2021-07-20 18:36:12 +08:00
|
|
|
}
|
|
|
|
|
2023-06-21 15:11:19 +08:00
|
|
|
protected override void PopOut()
|
|
|
|
{
|
|
|
|
skinEditor?.Save(false);
|
|
|
|
skinEditor?.Hide();
|
2024-02-19 16:45:03 +08:00
|
|
|
nestedInputManagerDisable?.Dispose();
|
|
|
|
nestedInputManagerDisable = null;
|
2023-06-21 15:11:19 +08:00
|
|
|
|
|
|
|
globallyReenableBeatmapSkinSetting();
|
|
|
|
}
|
2022-03-21 15:52:06 +08:00
|
|
|
|
2024-01-16 14:12:23 +08:00
|
|
|
public void PresentGameplay() => presentGameplay(false);
|
|
|
|
|
|
|
|
private void presentGameplay(bool attemptedBeatmapSwitch)
|
2023-11-24 16:19:03 +08:00
|
|
|
{
|
|
|
|
performer?.PerformFromScreen(screen =>
|
|
|
|
{
|
2024-01-16 14:12:23 +08:00
|
|
|
if (State.Value != Visibility.Visible)
|
|
|
|
return;
|
|
|
|
|
2023-12-04 21:20:03 +08:00
|
|
|
if (beatmap.Value is DummyWorkingBeatmap)
|
|
|
|
{
|
|
|
|
// presume we don't have anything good to play and just bail.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-24 16:34:08 +08:00
|
|
|
// If we're playing the intro, switch away to another beatmap.
|
|
|
|
if (beatmap.Value.BeatmapSetInfo.Protected)
|
|
|
|
{
|
2024-01-16 14:12:23 +08:00
|
|
|
if (!attemptedBeatmapSwitch)
|
|
|
|
{
|
|
|
|
music.NextTrack();
|
|
|
|
Schedule(() => presentGameplay(true));
|
|
|
|
}
|
|
|
|
|
2023-11-24 16:34:08 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
if (screen is Player)
|
|
|
|
return;
|
|
|
|
|
Forcibly change ruleset to correct one before entering gameplay from main menu
Closes #25663 (again).
As it turns out, in some scenarios it can be the case that the current
game-global `Beatmap` is not valid for the current game-global
`Ruleset`. The validity of one and the other in conjunction is only
really validated by the song select screen; elsewhere there is no
guarantee that the global beatmap is playable using the global ruleset.
However, this only comes up in very specific circumstances, namely one:
when trying to autoplay a catch beatmap with osu! ruleset globally
active via the skin editor flow.
`Player` is responsible for retrieving the beatmap to be played. It does
so by invoking the appropriate beatmap converter and asking it if the
beatmap can be converted:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Beatmaps/WorkingBeatmap.cs#L262-L266
If the code above throws, `Player` actually silently covers for this, by
trying the beatmap's default ruleset instead:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Screens/Play/Player.cs#L529-L536
However, for the pairing of osu! ruleset and catch beatmap, this fails,
as `OsuBeatmapConverter`'s condition necessary for permitting conversion
is that the objects have a defined position:
https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs#L25
which they will do, due to the fact that all catch beatmaps are really
just osu! beatmaps but with conversion steps applied, and thus `Player`
succeeds to load the catch beatmap in osu! ruleset.
In the skin editor scenario, this would lead to the secondary failure
of the skin editor trying to apply `CatchModAutoplay` on top of all
of that, which would fail at the hard-cast of the beatmap
to `CatchBeatmap`.
2023-12-06 17:12:25 +08:00
|
|
|
// the validity of the current game-wide beatmap + ruleset combination is enforced by song select.
|
|
|
|
// if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does.
|
|
|
|
if (screen is not PlaySongSelect)
|
|
|
|
ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset;
|
|
|
|
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
2023-11-24 16:19:03 +08:00
|
|
|
|
|
|
|
IReadOnlyList<Mod> usableMods = mods.Value;
|
|
|
|
|
|
|
|
if (replayGeneratingMod != null)
|
|
|
|
usableMods = usableMods.Append(replayGeneratingMod).ToArray();
|
|
|
|
|
|
|
|
if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid))
|
|
|
|
mods.Value = mods.Value.Except(invalid).ToArray();
|
|
|
|
|
|
|
|
if (replayGeneratingMod != null)
|
|
|
|
screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)));
|
|
|
|
}, new[] { typeof(Player), typeof(PlaySongSelect) });
|
|
|
|
}
|
|
|
|
|
2022-06-06 17:27:41 +08:00
|
|
|
protected override void Update()
|
|
|
|
{
|
|
|
|
base.Update();
|
|
|
|
|
|
|
|
if (game.DrawSize != lastDrawSize)
|
|
|
|
{
|
|
|
|
lastDrawSize = game.DrawSize;
|
|
|
|
updateScreenSizing();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-06 17:02:42 +08:00
|
|
|
private void updateScreenSizing()
|
|
|
|
{
|
|
|
|
if (skinEditor?.State.Value != Visibility.Visible) return;
|
|
|
|
|
2022-06-06 17:27:41 +08:00
|
|
|
const float padding = 10;
|
|
|
|
|
|
|
|
float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth;
|
|
|
|
float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight;
|
2022-06-06 17:02:42 +08:00
|
|
|
|
|
|
|
var rect = new RectangleF(
|
|
|
|
relativeSidebarWidth,
|
|
|
|
relativeToolbarHeight,
|
|
|
|
1 - relativeSidebarWidth * 2,
|
2022-06-06 17:27:41 +08:00
|
|
|
1f - relativeToolbarHeight - padding / DrawHeight);
|
2022-06-06 17:02:42 +08:00
|
|
|
|
|
|
|
scalingContainer.SetCustomRect(rect, true);
|
|
|
|
}
|
|
|
|
|
2022-03-16 18:11:17 +08:00
|
|
|
private void updateComponentVisibility()
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2022-03-15 17:10:30 +08:00
|
|
|
Debug.Assert(skinEditor != null);
|
|
|
|
|
2022-03-16 18:11:17 +08:00
|
|
|
if (skinEditor.State.Value == Visibility.Visible)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2022-06-06 17:02:42 +08:00
|
|
|
Scheduler.AddOnce(updateScreenSizing);
|
2022-03-15 17:10:30 +08:00
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
game.Toolbar.Hide();
|
|
|
|
game.CloseAllOverlays();
|
2021-04-29 16:20:22 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-03-15 17:00:32 +08:00
|
|
|
scalingContainer.SetCustomRect(null);
|
2022-03-15 17:10:30 +08:00
|
|
|
|
|
|
|
if (lastTargetScreen?.HideOverlaysOnEnter != true)
|
2023-01-26 16:46:19 +08:00
|
|
|
game.Toolbar.Show();
|
2021-04-29 16:20:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-16 17:26:12 +08:00
|
|
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2022-03-11 22:08:40 +08:00
|
|
|
/// Set a new target screen which will be used to find skinnable components.
|
2021-04-29 16:20:22 +08:00
|
|
|
/// </summary>
|
2022-03-15 17:10:30 +08:00
|
|
|
public void SetTarget(OsuScreen screen)
|
2021-04-29 16:20:22 +08:00
|
|
|
{
|
2024-02-19 16:45:03 +08:00
|
|
|
nestedInputManagerDisable?.Dispose();
|
|
|
|
nestedInputManagerDisable = null;
|
|
|
|
|
2023-06-21 15:11:19 +08:00
|
|
|
lastTargetScreen = screen;
|
2022-03-15 17:00:32 +08:00
|
|
|
|
2023-06-21 15:11:19 +08:00
|
|
|
if (skinEditor == null) return;
|
2022-03-11 22:08:40 +08:00
|
|
|
|
2023-06-21 15:11:19 +08:00
|
|
|
// ensure the toolbar is re-hidden even if a new screen decides to try and show it.
|
|
|
|
updateComponentVisibility();
|
2022-03-11 22:08:40 +08:00
|
|
|
|
2023-06-21 15:11:19 +08:00
|
|
|
// AddOnce with parameter will ensure the newest target is loaded if there is any overlap.
|
|
|
|
Scheduler.AddOnce(setTarget, screen);
|
2022-03-11 22:08:40 +08:00
|
|
|
}
|
2021-05-12 15:07:00 +08:00
|
|
|
|
2023-01-26 16:46:19 +08:00
|
|
|
private void setTarget(OsuScreen? target)
|
2022-03-11 22:08:40 +08:00
|
|
|
{
|
2022-06-06 17:26:52 +08:00
|
|
|
if (target == null)
|
|
|
|
return;
|
|
|
|
|
2022-03-11 22:08:40 +08:00
|
|
|
Debug.Assert(skinEditor != null);
|
|
|
|
|
2023-12-28 06:45:56 +08:00
|
|
|
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
2022-03-11 22:08:40 +08:00
|
|
|
{
|
|
|
|
Scheduler.AddOnce(setTarget, target);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skinEditor.State.Value == Visibility.Visible)
|
2023-11-21 16:49:56 +08:00
|
|
|
{
|
|
|
|
skinEditor.Save(false);
|
2022-03-11 22:08:40 +08:00
|
|
|
skinEditor.UpdateTargetScreen(target);
|
2024-02-19 16:45:03 +08:00
|
|
|
disableNestedInputManagers();
|
2023-11-21 16:49:56 +08:00
|
|
|
}
|
2022-03-11 22:08:40 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
skinEditor.Hide();
|
|
|
|
skinEditor.Expire();
|
|
|
|
skinEditor = null;
|
|
|
|
}
|
2021-04-29 16:20:22 +08:00
|
|
|
}
|
2023-06-20 18:18:17 +08:00
|
|
|
|
2024-02-19 16:45:03 +08:00
|
|
|
private void disableNestedInputManagers()
|
|
|
|
{
|
|
|
|
if (lastTargetScreen == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var nestedInputManagers = lastTargetScreen.ChildrenOfType<PassThroughInputManager>().Where(manager => manager.UseParentInput).ToArray();
|
|
|
|
foreach (var inputManager in nestedInputManagers)
|
|
|
|
inputManager.UseParentInput = false;
|
|
|
|
nestedInputManagerDisable = new InvokeOnDisposal(() =>
|
|
|
|
{
|
|
|
|
foreach (var inputManager in nestedInputManagers)
|
|
|
|
inputManager.UseParentInput = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-20 18:18:17 +08:00
|
|
|
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
2023-06-21 15:09:54 +08:00
|
|
|
private LeasedBindable<bool>? leasedBeatmapSkins;
|
2023-06-20 18:18:17 +08:00
|
|
|
|
|
|
|
private void globallyDisableBeatmapSkinSetting()
|
|
|
|
{
|
|
|
|
if (beatmapSkins.Disabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// The skin editor doesn't work well if beatmap skins are being applied to the player screen.
|
|
|
|
// To keep things simple, disable the setting game-wide while using the skin editor.
|
2023-11-23 15:39:05 +08:00
|
|
|
//
|
|
|
|
// This causes a full reload of the skin, which is pretty ugly.
|
|
|
|
// TODO: Investigate if we can avoid this when a beatmap skin is not being applied by the current beatmap.
|
2023-06-21 15:09:54 +08:00
|
|
|
leasedBeatmapSkins = beatmapSkins.BeginLease(true);
|
|
|
|
leasedBeatmapSkins.Value = false;
|
2023-06-20 18:18:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private void globallyReenableBeatmapSkinSetting()
|
|
|
|
{
|
2023-06-21 15:09:54 +08:00
|
|
|
leasedBeatmapSkins?.Return();
|
|
|
|
leasedBeatmapSkins = null;
|
2023-06-20 18:18:17 +08:00
|
|
|
}
|
2023-11-24 16:19:03 +08:00
|
|
|
|
|
|
|
private partial class EndlessPlayer : ReplayPlayer
|
|
|
|
{
|
2023-12-04 21:14:07 +08:00
|
|
|
protected override UserActivity? InitialActivity => null;
|
|
|
|
|
2023-12-06 16:18:35 +08:00
|
|
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
|
|
|
|
|
|
|
public override bool? AllowGlobalTrackControl => false;
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
public EndlessPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore)
|
|
|
|
: base(createScore, new PlayerConfiguration
|
|
|
|
{
|
|
|
|
ShowResults = false,
|
2023-11-24 16:34:08 +08:00
|
|
|
AutomaticallySkipIntro = true,
|
2023-11-24 16:19:03 +08:00
|
|
|
})
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-12-04 21:14:26 +08:00
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
|
|
|
if (!LoadedBeatmapSuccessfully)
|
2023-12-27 04:46:50 +08:00
|
|
|
Scheduler.AddDelayed(this.Exit, 1000);
|
2023-12-04 21:14:26 +08:00
|
|
|
}
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
protected override void Update()
|
|
|
|
{
|
|
|
|
base.Update();
|
|
|
|
|
2023-12-04 21:14:26 +08:00
|
|
|
if (!LoadedBeatmapSuccessfully)
|
|
|
|
return;
|
|
|
|
|
2023-11-24 16:19:03 +08:00
|
|
|
if (GameplayState.HasPassed)
|
|
|
|
GameplayClockContainer.Seek(0);
|
|
|
|
}
|
|
|
|
}
|
2021-04-29 16:20:22 +08:00
|
|
|
}
|
|
|
|
}
|