mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge branch 'master' into user-rank-card
This commit is contained in:
commit
b86c883a5d
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1227.1" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.114.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
739
osu.Desktop/NVAPI.cs
Normal file
739
osu.Desktop/NVAPI.cs
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // Naming rule violation
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
|
||||||
|
namespace osu.Desktop
|
||||||
|
{
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
internal static class NVAPI
|
||||||
|
{
|
||||||
|
private const string osu_filename = "osu!.exe";
|
||||||
|
|
||||||
|
// This is a good reference:
|
||||||
|
// https://github.com/errollw/Warp-and-Blend-Quadros/blob/master/WarpBlend-Quadros/UnwarpAll-Quadros/include/nvapi.h
|
||||||
|
// Note our Stride == their VERSION (e.g. NVDRS_SETTING_VER)
|
||||||
|
|
||||||
|
public const int MAX_PHYSICAL_GPUS = 64;
|
||||||
|
public const int UNICODE_STRING_MAX = 2048;
|
||||||
|
|
||||||
|
public const string APPLICATION_NAME = @"osu!";
|
||||||
|
public const string PROFILE_NAME = @"osu!";
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus EnumPhysicalGPUsDelegate([Out] IntPtr[] gpuHandles, out int gpuCount);
|
||||||
|
|
||||||
|
public static readonly EnumPhysicalGPUsDelegate EnumPhysicalGPUs;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus EnumLogicalGPUsDelegate([Out] IntPtr[] gpuHandles, out int gpuCount);
|
||||||
|
|
||||||
|
public static readonly EnumLogicalGPUsDelegate EnumLogicalGPUs;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus GetSystemTypeDelegate(IntPtr gpuHandle, out NvSystemType systemType);
|
||||||
|
|
||||||
|
public static readonly GetSystemTypeDelegate GetSystemType;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus GetGPUTypeDelegate(IntPtr gpuHandle, out NvGpuType gpuType);
|
||||||
|
|
||||||
|
public static readonly GetGPUTypeDelegate GetGPUType;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus CreateSessionDelegate(out IntPtr sessionHandle);
|
||||||
|
|
||||||
|
public static CreateSessionDelegate CreateSession;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus LoadSettingsDelegate(IntPtr sessionHandle);
|
||||||
|
|
||||||
|
public static LoadSettingsDelegate LoadSettings;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus FindApplicationByNameDelegate(IntPtr sessionHandle, [MarshalAs(UnmanagedType.BStr)] string appName, out IntPtr profileHandle, ref NvApplication application);
|
||||||
|
|
||||||
|
public static FindApplicationByNameDelegate FindApplicationByName;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus GetCurrentGlobalProfileDelegate(IntPtr sessionHandle, out IntPtr profileHandle);
|
||||||
|
|
||||||
|
public static GetCurrentGlobalProfileDelegate GetCurrentGlobalProfile;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus GetProfileInfoDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvProfile profile);
|
||||||
|
|
||||||
|
public static GetProfileInfoDelegate GetProfileInfo;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate NvStatus GetSettingDelegate(IntPtr sessionHandle, IntPtr profileHandle, NvSettingID settingID, ref NvSetting setting);
|
||||||
|
|
||||||
|
public static GetSettingDelegate GetSetting;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate NvStatus CreateProfileDelegate(IntPtr sessionHandle, ref NvProfile profile, out IntPtr profileHandle);
|
||||||
|
|
||||||
|
private static readonly CreateProfileDelegate CreateProfile;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate NvStatus SetSettingDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvSetting setting);
|
||||||
|
|
||||||
|
private static readonly SetSettingDelegate SetSetting;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate NvStatus EnumApplicationsDelegate(IntPtr sessionHandle, IntPtr profileHandle, uint startIndex, ref uint appCount, [In, Out, MarshalAs(UnmanagedType.LPArray)] NvApplication[] applications);
|
||||||
|
|
||||||
|
private static readonly EnumApplicationsDelegate EnumApplications;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate NvStatus CreateApplicationDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvApplication application);
|
||||||
|
|
||||||
|
private static readonly CreateApplicationDelegate CreateApplication;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate NvStatus SaveSettingsDelegate(IntPtr sessionHandle);
|
||||||
|
|
||||||
|
private static readonly SaveSettingsDelegate SaveSettings;
|
||||||
|
|
||||||
|
public static NvStatus Status { get; private set; } = NvStatus.OK;
|
||||||
|
public static bool Available { get; private set; }
|
||||||
|
|
||||||
|
private static IntPtr sessionHandle;
|
||||||
|
|
||||||
|
public static bool IsUsingOptimusDedicatedGpu
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!IsLaptop)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IntPtr profileHandle;
|
||||||
|
if (!getProfile(out profileHandle, out _, out bool _))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Get the optimus setting
|
||||||
|
NvSetting setting;
|
||||||
|
if (!getSetting(NvSettingID.SHIM_RENDERING_MODE_ID, profileHandle, out setting))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (setting.U32CurrentValue & (uint)NvShimSetting.SHIM_RENDERING_MODE_ENABLE) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsLaptop
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Make sure that this is a laptop.
|
||||||
|
var gpus = new IntPtr[64];
|
||||||
|
if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < gpuCount; i++)
|
||||||
|
{
|
||||||
|
if (checkError(GetSystemType(gpus[i], out var type)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (type == NvSystemType.LAPTOP)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NvThreadControlSetting ThreadedOptimisations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||||
|
|
||||||
|
IntPtr profileHandle;
|
||||||
|
if (!getProfile(out profileHandle, out _, out bool _))
|
||||||
|
return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||||
|
|
||||||
|
// Get the threaded optimisations setting
|
||||||
|
NvSetting setting;
|
||||||
|
if (!getSetting(NvSettingID.OGL_THREAD_CONTROL_ID, profileHandle, out setting))
|
||||||
|
return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||||
|
|
||||||
|
return (NvThreadControlSetting)setting.U32CurrentValue;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool success = setSetting(NvSettingID.OGL_THREAD_CONTROL_ID, (uint)value);
|
||||||
|
|
||||||
|
Logger.Log(success ? $"Threaded optimizations set to \"{value}\"!" : "Threaded optimizations set failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the profile contains the current application.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>If the profile contains the current application.</returns>
|
||||||
|
private static bool containsApplication(IntPtr profileHandle, NvProfile profile, out NvApplication application)
|
||||||
|
{
|
||||||
|
application = new NvApplication
|
||||||
|
{
|
||||||
|
Version = NvApplication.Stride
|
||||||
|
};
|
||||||
|
|
||||||
|
if (profile.NumOfApps == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NvApplication[] applications = new NvApplication[profile.NumOfApps];
|
||||||
|
applications[0].Version = NvApplication.Stride;
|
||||||
|
|
||||||
|
uint numApps = profile.NumOfApps;
|
||||||
|
|
||||||
|
if (checkError(EnumApplications(sessionHandle, profileHandle, 0, ref numApps, applications)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (uint i = 0; i < numApps; i++)
|
||||||
|
{
|
||||||
|
if (applications[i].AppName == osu_filename)
|
||||||
|
{
|
||||||
|
application = applications[i];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the profile of the current application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileHandle">The profile handle.</param>
|
||||||
|
/// <param name="application">The current application description.</param>
|
||||||
|
/// <param name="isApplicationSpecific">If this profile is not a global (default) profile.</param>
|
||||||
|
/// <returns>If the operation succeeded.</returns>
|
||||||
|
private static bool getProfile(out IntPtr profileHandle, out NvApplication application, out bool isApplicationSpecific)
|
||||||
|
{
|
||||||
|
application = new NvApplication
|
||||||
|
{
|
||||||
|
Version = NvApplication.Stride
|
||||||
|
};
|
||||||
|
|
||||||
|
isApplicationSpecific = true;
|
||||||
|
|
||||||
|
if (checkError(FindApplicationByName(sessionHandle, osu_filename, out profileHandle, ref application)))
|
||||||
|
{
|
||||||
|
isApplicationSpecific = false;
|
||||||
|
if (checkError(GetCurrentGlobalProfile(sessionHandle, out profileHandle)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileHandle">The profile handle.</param>
|
||||||
|
/// <returns>If the operation succeeded.</returns>
|
||||||
|
private static bool createProfile(out IntPtr profileHandle)
|
||||||
|
{
|
||||||
|
NvProfile newProfile = new NvProfile
|
||||||
|
{
|
||||||
|
Version = NvProfile.Stride,
|
||||||
|
IsPredefined = 0,
|
||||||
|
ProfileName = PROFILE_NAME,
|
||||||
|
GPUSupport = new uint[32]
|
||||||
|
};
|
||||||
|
|
||||||
|
newProfile.GPUSupport[0] = 1;
|
||||||
|
|
||||||
|
if (checkError(CreateProfile(sessionHandle, ref newProfile, out profileHandle)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a setting from the profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingId">The setting to retrieve.</param>
|
||||||
|
/// <param name="profileHandle">The profile handle to retrieve the setting from.</param>
|
||||||
|
/// <param name="setting">The setting.</param>
|
||||||
|
/// <returns>If the operation succeeded.</returns>
|
||||||
|
private static bool getSetting(NvSettingID settingId, IntPtr profileHandle, out NvSetting setting)
|
||||||
|
{
|
||||||
|
setting = new NvSetting
|
||||||
|
{
|
||||||
|
Version = NvSetting.Stride,
|
||||||
|
SettingID = settingId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkError(GetSetting(sessionHandle, profileHandle, settingId, ref setting)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool setSetting(NvSettingID settingId, uint settingValue)
|
||||||
|
{
|
||||||
|
NvApplication application;
|
||||||
|
IntPtr profileHandle;
|
||||||
|
bool isApplicationSpecific;
|
||||||
|
if (!getProfile(out profileHandle, out application, out isApplicationSpecific))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!isApplicationSpecific)
|
||||||
|
{
|
||||||
|
// We don't want to interfere with the user's other settings, so let's create a separate config for osu!
|
||||||
|
if (!createProfile(out profileHandle))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvSetting newSetting = new NvSetting
|
||||||
|
{
|
||||||
|
Version = NvSetting.Stride,
|
||||||
|
SettingID = settingId,
|
||||||
|
U32CurrentValue = settingValue
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the thread state
|
||||||
|
if (checkError(SetSetting(sessionHandle, profileHandle, ref newSetting)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Get the profile (needed to check app count)
|
||||||
|
NvProfile profile = new NvProfile
|
||||||
|
{
|
||||||
|
Version = NvProfile.Stride
|
||||||
|
};
|
||||||
|
if (checkError(GetProfileInfo(sessionHandle, profileHandle, ref profile)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!containsApplication(profileHandle, profile, out application))
|
||||||
|
{
|
||||||
|
// Need to add the current application to the profile
|
||||||
|
application.IsPredefined = 0;
|
||||||
|
|
||||||
|
application.AppName = osu_filename;
|
||||||
|
application.UserFriendlyName = APPLICATION_NAME;
|
||||||
|
|
||||||
|
if (checkError(CreateApplication(sessionHandle, profileHandle, ref application)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save!
|
||||||
|
return !checkError(SaveSettings(sessionHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a session to access the driver configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>If the operation succeeded.</returns>
|
||||||
|
private static bool createSession()
|
||||||
|
{
|
||||||
|
if (checkError(CreateSession(out sessionHandle)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Load settings into session
|
||||||
|
if (checkError(LoadSettings(sessionHandle)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool checkError(NvStatus status)
|
||||||
|
{
|
||||||
|
Status = status;
|
||||||
|
return status != NvStatus.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NVAPI()
|
||||||
|
{
|
||||||
|
// TODO: check whether gpu vendor contains NVIDIA before attempting load?
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to load NVAPI
|
||||||
|
if ((IntPtr.Size == 4 && loadLibrary(@"nvapi.dll") == IntPtr.Zero)
|
||||||
|
|| (IntPtr.Size == 8 && loadLibrary(@"nvapi64.dll") == IntPtr.Zero))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeDelegate initialize;
|
||||||
|
getDelegate(0x0150E828, out initialize);
|
||||||
|
|
||||||
|
if (initialize?.Invoke() == NvStatus.OK)
|
||||||
|
{
|
||||||
|
// IDs can be found here: https://github.com/jNizM/AHK_NVIDIA_NvAPI/blob/master/info/NvAPI_IDs.txt
|
||||||
|
|
||||||
|
getDelegate(0xE5AC921F, out EnumPhysicalGPUs);
|
||||||
|
getDelegate(0x48B3EA59, out EnumLogicalGPUs);
|
||||||
|
getDelegate(0xBAAABFCC, out GetSystemType);
|
||||||
|
getDelegate(0xC33BAEB1, out GetGPUType);
|
||||||
|
getDelegate(0x0694D52E, out CreateSession);
|
||||||
|
getDelegate(0x375DBD6B, out LoadSettings);
|
||||||
|
getDelegate(0xEEE566B2, out FindApplicationByName);
|
||||||
|
getDelegate(0x617BFF9F, out GetCurrentGlobalProfile);
|
||||||
|
getDelegate(0x577DD202, out SetSetting);
|
||||||
|
getDelegate(0x61CD6FD6, out GetProfileInfo);
|
||||||
|
getDelegate(0x73BF8338, out GetSetting);
|
||||||
|
getDelegate(0xCC176068, out CreateProfile);
|
||||||
|
getDelegate(0x7FA2173A, out EnumApplications);
|
||||||
|
getDelegate(0x4347A9DE, out CreateApplication);
|
||||||
|
getDelegate(0xFCBC7E14, out SaveSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createSession())
|
||||||
|
Available = true;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void getDelegate<T>(uint id, out T newDelegate) where T : class
|
||||||
|
{
|
||||||
|
IntPtr ptr = IntPtr.Size == 4 ? queryInterface32(id) : queryInterface64(id);
|
||||||
|
newDelegate = ptr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
|
||||||
|
private static extern IntPtr loadLibrary(string dllToLoad);
|
||||||
|
|
||||||
|
[DllImport(@"nvapi.dll", EntryPoint = "nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern IntPtr queryInterface32(uint id);
|
||||||
|
|
||||||
|
[DllImport(@"nvapi64.dll", EntryPoint = "nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern IntPtr queryInterface64(uint id);
|
||||||
|
|
||||||
|
private delegate NvStatus InitializeDelegate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
internal struct NvSetting
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string SettingName;
|
||||||
|
|
||||||
|
public NvSettingID SettingID;
|
||||||
|
public uint SettingType;
|
||||||
|
public uint SettingLocation;
|
||||||
|
public uint IsCurrentPredefined;
|
||||||
|
public uint IsPredefinedValid;
|
||||||
|
|
||||||
|
public uint U32PredefinedValue;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string StringPredefinedValue;
|
||||||
|
|
||||||
|
public uint U32CurrentValue;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string StringCurrentValue;
|
||||||
|
|
||||||
|
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvSetting)) | (1 << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
|
||||||
|
internal struct NvProfile
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string ProfileName;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray)]
|
||||||
|
public uint[] GPUSupport;
|
||||||
|
|
||||||
|
public uint IsPredefined;
|
||||||
|
public uint NumOfApps;
|
||||||
|
public uint NumOfSettings;
|
||||||
|
|
||||||
|
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvProfile)) | (1 << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
|
||||||
|
internal struct NvApplication
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
public uint IsPredefined;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string AppName;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string UserFriendlyName;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string Launcher;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
|
public string FileInFolder;
|
||||||
|
|
||||||
|
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvStatus
|
||||||
|
{
|
||||||
|
OK = 0, // Success. Request is completed.
|
||||||
|
ERROR = -1, // Generic error
|
||||||
|
LIBRARY_NOT_FOUND = -2, // NVAPI support library cannot be loaded.
|
||||||
|
NO_IMPLEMENTATION = -3, // not implemented in current driver installation
|
||||||
|
API_NOT_INITIALIZED = -4, // Initialize has not been called (successfully)
|
||||||
|
INVALID_ARGUMENT = -5, // The argument/parameter value is not valid or NULL.
|
||||||
|
NVIDIA_DEVICE_NOT_FOUND = -6, // No NVIDIA display driver, or NVIDIA GPU driving a display, was found.
|
||||||
|
END_ENUMERATION = -7, // No more items to enumerate
|
||||||
|
INVALID_HANDLE = -8, // Invalid handle
|
||||||
|
INCOMPATIBLE_STRUCT_VERSION = -9, // An argument's structure version is not supported
|
||||||
|
HANDLE_INVALIDATED = -10, // The handle is no longer valid (likely due to GPU or display re-configuration)
|
||||||
|
OPENGL_CONTEXT_NOT_CURRENT = -11, // No NVIDIA OpenGL context is current (but needs to be)
|
||||||
|
INVALID_POINTER = -14, // An invalid pointer, usually NULL, was passed as a parameter
|
||||||
|
NO_GL_EXPERT = -12, // OpenGL Expert is not supported by the current drivers
|
||||||
|
INSTRUMENTATION_DISABLED = -13, // OpenGL Expert is supported, but driver instrumentation is currently disabled
|
||||||
|
NO_GL_NSIGHT = -15, // OpenGL does not support Nsight
|
||||||
|
|
||||||
|
EXPECTED_LOGICAL_GPU_HANDLE = -100, // Expected a logical GPU handle for one or more parameters
|
||||||
|
EXPECTED_PHYSICAL_GPU_HANDLE = -101, // Expected a physical GPU handle for one or more parameters
|
||||||
|
EXPECTED_DISPLAY_HANDLE = -102, // Expected an NV display handle for one or more parameters
|
||||||
|
INVALID_COMBINATION = -103, // The combination of parameters is not valid.
|
||||||
|
NOT_SUPPORTED = -104, // Requested feature is not supported in the selected GPU
|
||||||
|
PORTID_NOT_FOUND = -105, // No port ID was found for the I2C transaction
|
||||||
|
EXPECTED_UNATTACHED_DISPLAY_HANDLE = -106, // Expected an unattached display handle as one of the input parameters.
|
||||||
|
INVALID_PERF_LEVEL = -107, // Invalid perf level
|
||||||
|
DEVICE_BUSY = -108, // Device is busy; request not fulfilled
|
||||||
|
NV_PERSIST_FILE_NOT_FOUND = -109, // NV persist file is not found
|
||||||
|
PERSIST_DATA_NOT_FOUND = -110, // NV persist data is not found
|
||||||
|
EXPECTED_TV_DISPLAY = -111, // Expected a TV output display
|
||||||
|
EXPECTED_TV_DISPLAY_ON_DCONNECTOR = -112, // Expected a TV output on the D Connector - HDTV_EIAJ4120.
|
||||||
|
NO_ACTIVE_SLI_TOPOLOGY = -113, // SLI is not active on this device.
|
||||||
|
SLI_RENDERING_MODE_NOTALLOWED = -114, // Setup of SLI rendering mode is not possible right now.
|
||||||
|
EXPECTED_DIGITAL_FLAT_PANEL = -115, // Expected a digital flat panel.
|
||||||
|
ARGUMENT_EXCEED_MAX_SIZE = -116, // Argument exceeds the expected size.
|
||||||
|
DEVICE_SWITCHING_NOT_ALLOWED = -117, // Inhibit is ON due to one of the flags in NV_GPU_DISPLAY_CHANGE_INHIBIT or SLI active.
|
||||||
|
TESTING_CLOCKS_NOT_SUPPORTED = -118, // Testing of clocks is not supported.
|
||||||
|
UNKNOWN_UNDERSCAN_CONFIG = -119, // The specified underscan config is from an unknown source (e.g. INF)
|
||||||
|
TIMEOUT_RECONFIGURING_GPU_TOPO = -120, // Timeout while reconfiguring GPUs
|
||||||
|
DATA_NOT_FOUND = -121, // Requested data was not found
|
||||||
|
EXPECTED_ANALOG_DISPLAY = -122, // Expected an analog display
|
||||||
|
NO_VIDLINK = -123, // No SLI video bridge is present
|
||||||
|
REQUIRES_REBOOT = -124, // NVAPI requires a reboot for the settings to take effect
|
||||||
|
INVALID_HYBRID_MODE = -125, // The function is not supported with the current Hybrid mode.
|
||||||
|
MIXED_TARGET_TYPES = -126, // The target types are not all the same
|
||||||
|
SYSWOW64_NOT_SUPPORTED = -127, // The function is not supported from 32-bit on a 64-bit system.
|
||||||
|
IMPLICIT_SET_GPU_TOPOLOGY_CHANGE_NOT_ALLOWED = -128, // There is no implicit GPU topology active. Use SetHybridMode to change topology.
|
||||||
|
REQUEST_USER_TO_CLOSE_NON_MIGRATABLE_APPS = -129, // Prompt the user to close all non-migratable applications.
|
||||||
|
OUT_OF_MEMORY = -130, // Could not allocate sufficient memory to complete the call.
|
||||||
|
WAS_STILL_DRAWING = -131, // The previous operation that is transferring information to or from this surface is incomplete.
|
||||||
|
FILE_NOT_FOUND = -132, // The file was not found.
|
||||||
|
TOO_MANY_UNIQUE_STATE_OBJECTS = -133, // There are too many unique instances of a particular type of state object.
|
||||||
|
INVALID_CALL = -134, // The method call is invalid. For example, a method's parameter may not be a valid pointer.
|
||||||
|
D3D10_1_LIBRARY_NOT_FOUND = -135, // d3d10_1.dll cannot be loaded.
|
||||||
|
FUNCTION_NOT_FOUND = -136, // Couldn't find the function in the loaded DLL.
|
||||||
|
INVALID_USER_PRIVILEGE = -137, // Current User is not Admin.
|
||||||
|
EXPECTED_NON_PRIMARY_DISPLAY_HANDLE = -138, // The handle corresponds to GDIPrimary.
|
||||||
|
EXPECTED_COMPUTE_GPU_HANDLE = -139, // Setting Physx GPU requires that the GPU is compute-capable.
|
||||||
|
STEREO_NOT_INITIALIZED = -140, // The Stereo part of NVAPI failed to initialize completely. Check if the stereo driver is installed.
|
||||||
|
STEREO_REGISTRY_ACCESS_FAILED = -141, // Access to stereo-related registry keys or values has failed.
|
||||||
|
STEREO_REGISTRY_PROFILE_TYPE_NOT_SUPPORTED = -142, // The given registry profile type is not supported.
|
||||||
|
STEREO_REGISTRY_VALUE_NOT_SUPPORTED = -143, // The given registry value is not supported.
|
||||||
|
STEREO_NOT_ENABLED = -144, // Stereo is not enabled and the function needed it to execute completely.
|
||||||
|
STEREO_NOT_TURNED_ON = -145, // Stereo is not turned on and the function needed it to execute completely.
|
||||||
|
STEREO_INVALID_DEVICE_INTERFACE = -146, // Invalid device interface.
|
||||||
|
STEREO_PARAMETER_OUT_OF_RANGE = -147, // Separation percentage or JPEG image capture quality is out of [0-100] range.
|
||||||
|
STEREO_FRUSTUM_ADJUST_MODE_NOT_SUPPORTED = -148, // The given frustum adjust mode is not supported.
|
||||||
|
TOPO_NOT_POSSIBLE = -149, // The mosaic topology is not possible given the current state of the hardware.
|
||||||
|
MODE_CHANGE_FAILED = -150, // An attempt to do a display resolution mode change has failed.
|
||||||
|
D3D11_LIBRARY_NOT_FOUND = -151, // d3d11.dll/d3d11_beta.dll cannot be loaded.
|
||||||
|
INVALID_ADDRESS = -152, // Address is outside of valid range.
|
||||||
|
STRING_TOO_SMALL = -153, // The pre-allocated string is too small to hold the result.
|
||||||
|
MATCHING_DEVICE_NOT_FOUND = -154, // The input does not match any of the available devices.
|
||||||
|
DRIVER_RUNNING = -155, // Driver is running.
|
||||||
|
DRIVER_NOTRUNNING = -156, // Driver is not running.
|
||||||
|
ERROR_DRIVER_RELOAD_REQUIRED = -157, // A driver reload is required to apply these settings.
|
||||||
|
SET_NOT_ALLOWED = -158, // Intended setting is not allowed.
|
||||||
|
ADVANCED_DISPLAY_TOPOLOGY_REQUIRED = -159, // Information can't be returned due to "advanced display topology".
|
||||||
|
SETTING_NOT_FOUND = -160, // Setting is not found.
|
||||||
|
SETTING_SIZE_TOO_LARGE = -161, // Setting size is too large.
|
||||||
|
TOO_MANY_SETTINGS_IN_PROFILE = -162, // There are too many settings for a profile.
|
||||||
|
PROFILE_NOT_FOUND = -163, // Profile is not found.
|
||||||
|
PROFILE_NAME_IN_USE = -164, // Profile name is duplicated.
|
||||||
|
PROFILE_NAME_EMPTY = -165, // Profile name is empty.
|
||||||
|
EXECUTABLE_NOT_FOUND = -166, // Application not found in the Profile.
|
||||||
|
EXECUTABLE_ALREADY_IN_USE = -167, // Application already exists in the other profile.
|
||||||
|
DATATYPE_MISMATCH = -168, // Data Type mismatch
|
||||||
|
PROFILE_REMOVED = -169, // The profile passed as parameter has been removed and is no longer valid.
|
||||||
|
UNREGISTERED_RESOURCE = -170, // An unregistered resource was passed as a parameter.
|
||||||
|
ID_OUT_OF_RANGE = -171, // The DisplayId corresponds to a display which is not within the normal outputId range.
|
||||||
|
DISPLAYCONFIG_VALIDATION_FAILED = -172, // Display topology is not valid so the driver cannot do a mode set on this configuration.
|
||||||
|
DPMST_CHANGED = -173, // Display Port Multi-Stream topology has been changed.
|
||||||
|
INSUFFICIENT_BUFFER = -174, // Input buffer is insufficient to hold the contents.
|
||||||
|
ACCESS_DENIED = -175, // No access to the caller.
|
||||||
|
MOSAIC_NOT_ACTIVE = -176, // The requested action cannot be performed without Mosaic being enabled.
|
||||||
|
SHARE_RESOURCE_RELOCATED = -177, // The surface is relocated away from video memory.
|
||||||
|
REQUEST_USER_TO_DISABLE_DWM = -178, // The user should disable DWM before calling NvAPI.
|
||||||
|
D3D_DEVICE_LOST = -179, // D3D device status is D3DERR_DEVICELOST or D3DERR_DEVICENOTRESET - the user has to reset the device.
|
||||||
|
INVALID_CONFIGURATION = -180, // The requested action cannot be performed in the current state.
|
||||||
|
STEREO_HANDSHAKE_NOT_DONE = -181, // Call failed as stereo handshake not completed.
|
||||||
|
EXECUTABLE_PATH_IS_AMBIGUOUS = -182, // The path provided was too short to determine the correct NVDRS_APPLICATION
|
||||||
|
DEFAULT_STEREO_PROFILE_IS_NOT_DEFINED = -183, // Default stereo profile is not currently defined
|
||||||
|
DEFAULT_STEREO_PROFILE_DOES_NOT_EXIST = -184, // Default stereo profile does not exist
|
||||||
|
CLUSTER_ALREADY_EXISTS = -185, // A cluster is already defined with the given configuration.
|
||||||
|
DPMST_DISPLAY_ID_EXPECTED = -186, // The input display id is not that of a multi stream enabled connector or a display device in a multi stream topology
|
||||||
|
INVALID_DISPLAY_ID = -187, // The input display id is not valid or the monitor associated to it does not support the current operation
|
||||||
|
STREAM_IS_OUT_OF_SYNC = -188, // While playing secure audio stream, stream goes out of sync
|
||||||
|
INCOMPATIBLE_AUDIO_DRIVER = -189, // Older audio driver version than required
|
||||||
|
VALUE_ALREADY_SET = -190, // Value already set, setting again not allowed.
|
||||||
|
TIMEOUT = -191, // Requested operation timed out
|
||||||
|
GPU_WORKSTATION_FEATURE_INCOMPLETE = -192, // The requested workstation feature set has incomplete driver internal allocation resources
|
||||||
|
STEREO_INIT_ACTIVATION_NOT_DONE = -193, // Call failed because InitActivation was not called.
|
||||||
|
SYNC_NOT_ACTIVE = -194, // The requested action cannot be performed without Sync being enabled.
|
||||||
|
SYNC_MASTER_NOT_FOUND = -195, // The requested action cannot be performed without Sync Master being enabled.
|
||||||
|
INVALID_SYNC_TOPOLOGY = -196, // Invalid displays passed in the NV_GSYNC_DISPLAY pointer.
|
||||||
|
ECID_SIGN_ALGO_UNSUPPORTED = -197, // The specified signing algorithm is not supported. Either an incorrect value was entered or the current installed driver/hardware does not support the input value.
|
||||||
|
ECID_KEY_VERIFICATION_FAILED = -198, // The encrypted public key verification has failed.
|
||||||
|
FIRMWARE_OUT_OF_DATE = -199, // The device's firmware is out of date.
|
||||||
|
FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvSystemType
|
||||||
|
{
|
||||||
|
UNKNOWN = 0,
|
||||||
|
LAPTOP = 1,
|
||||||
|
DESKTOP = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvGpuType
|
||||||
|
{
|
||||||
|
UNKNOWN = 0,
|
||||||
|
IGPU = 1, // Integrated
|
||||||
|
DGPU = 2, // Discrete
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvSettingID : uint
|
||||||
|
{
|
||||||
|
OGL_AA_LINE_GAMMA_ID = 0x2089BF6C,
|
||||||
|
OGL_DEEP_COLOR_SCANOUT_ID = 0x2097C2F6,
|
||||||
|
OGL_DEFAULT_SWAP_INTERVAL_ID = 0x206A6582,
|
||||||
|
OGL_DEFAULT_SWAP_INTERVAL_FRACTIONAL_ID = 0x206C4581,
|
||||||
|
OGL_DEFAULT_SWAP_INTERVAL_SIGN_ID = 0x20655CFA,
|
||||||
|
OGL_EVENT_LOG_SEVERITY_THRESHOLD_ID = 0x209DF23E,
|
||||||
|
OGL_EXTENSION_STRING_VERSION_ID = 0x20FF7493,
|
||||||
|
OGL_FORCE_BLIT_ID = 0x201F619F,
|
||||||
|
OGL_FORCE_STEREO_ID = 0x204D9A0C,
|
||||||
|
OGL_IMPLICIT_GPU_AFFINITY_ID = 0x20D0F3E6,
|
||||||
|
OGL_MAX_FRAMES_ALLOWED_ID = 0x208E55E3,
|
||||||
|
OGL_MULTIMON_ID = 0x200AEBFC,
|
||||||
|
OGL_OVERLAY_PIXEL_TYPE_ID = 0x209AE66F,
|
||||||
|
OGL_OVERLAY_SUPPORT_ID = 0x206C28C4,
|
||||||
|
OGL_QUALITY_ENHANCEMENTS_ID = 0x20797D6C,
|
||||||
|
OGL_SINGLE_BACKDEPTH_BUFFER_ID = 0x20A29055,
|
||||||
|
OGL_THREAD_CONTROL_ID = 0x20C1221E,
|
||||||
|
OGL_TRIPLE_BUFFER_ID = 0x20FDD1F9,
|
||||||
|
OGL_VIDEO_EDITING_MODE_ID = 0x20EE02B4,
|
||||||
|
AA_BEHAVIOR_FLAGS_ID = 0x10ECDB82,
|
||||||
|
AA_MODE_ALPHATOCOVERAGE_ID = 0x10FC2D9C,
|
||||||
|
AA_MODE_GAMMACORRECTION_ID = 0x107D639D,
|
||||||
|
AA_MODE_METHOD_ID = 0x10D773D2,
|
||||||
|
AA_MODE_REPLAY_ID = 0x10D48A85,
|
||||||
|
AA_MODE_SELECTOR_ID = 0x107EFC5B,
|
||||||
|
AA_MODE_SELECTOR_SLIAA_ID = 0x107AFC5B,
|
||||||
|
ANISO_MODE_LEVEL_ID = 0x101E61A9,
|
||||||
|
ANISO_MODE_SELECTOR_ID = 0x10D2BB16,
|
||||||
|
APPLICATION_PROFILE_NOTIFICATION_TIMEOUT_ID = 0x104554B6,
|
||||||
|
APPLICATION_STEAM_ID_ID = 0x107CDDBC,
|
||||||
|
CPL_HIDDEN_PROFILE_ID = 0x106D5CFF,
|
||||||
|
CUDA_EXCLUDED_GPUS_ID = 0x10354FF8,
|
||||||
|
D3DOGL_GPU_MAX_POWER_ID = 0x10D1EF29,
|
||||||
|
EXPORT_PERF_COUNTERS_ID = 0x108F0841,
|
||||||
|
FXAA_ALLOW_ID = 0x1034CB89,
|
||||||
|
FXAA_ENABLE_ID = 0x1074C972,
|
||||||
|
FXAA_INDICATOR_ENABLE_ID = 0x1068FB9C,
|
||||||
|
MCSFRSHOWSPLIT_ID = 0x10287051,
|
||||||
|
OPTIMUS_MAXAA_ID = 0x10F9DC83,
|
||||||
|
PHYSXINDICATOR_ID = 0x1094F16F,
|
||||||
|
PREFERRED_PSTATE_ID = 0x1057EB71,
|
||||||
|
PREVENT_UI_AF_OVERRIDE_ID = 0x103BCCB5,
|
||||||
|
PS_FRAMERATE_LIMITER_ID = 0x10834FEE,
|
||||||
|
PS_FRAMERATE_LIMITER_GPS_CTRL_ID = 0x10834F01,
|
||||||
|
SHIM_MAXRES_ID = 0x10F9DC82,
|
||||||
|
SHIM_MCCOMPAT_ID = 0x10F9DC80,
|
||||||
|
SHIM_RENDERING_MODE_ID = 0x10F9DC81,
|
||||||
|
SHIM_RENDERING_OPTIONS_ID = 0x10F9DC84,
|
||||||
|
SLI_GPU_COUNT_ID = 0x1033DCD1,
|
||||||
|
SLI_PREDEFINED_GPU_COUNT_ID = 0x1033DCD2,
|
||||||
|
SLI_PREDEFINED_GPU_COUNT_DX10_ID = 0x1033DCD3,
|
||||||
|
SLI_PREDEFINED_MODE_ID = 0x1033CEC1,
|
||||||
|
SLI_PREDEFINED_MODE_DX10_ID = 0x1033CEC2,
|
||||||
|
SLI_RENDERING_MODE_ID = 0x1033CED1,
|
||||||
|
VRRFEATUREINDICATOR_ID = 0x1094F157,
|
||||||
|
VRROVERLAYINDICATOR_ID = 0x1095F16F,
|
||||||
|
VRRREQUESTSTATE_ID = 0x1094F1F7,
|
||||||
|
VSYNCSMOOTHAFR_ID = 0x101AE763,
|
||||||
|
VSYNCVRRCONTROL_ID = 0x10A879CE,
|
||||||
|
VSYNC_BEHAVIOR_FLAGS_ID = 0x10FDEC23,
|
||||||
|
WKS_API_STEREO_EYES_EXCHANGE_ID = 0x11AE435C,
|
||||||
|
WKS_API_STEREO_MODE_ID = 0x11E91A61,
|
||||||
|
WKS_MEMORY_ALLOCATION_POLICY_ID = 0x11112233,
|
||||||
|
WKS_STEREO_DONGLE_SUPPORT_ID = 0x112493BD,
|
||||||
|
WKS_STEREO_SUPPORT_ID = 0x11AA9E99,
|
||||||
|
WKS_STEREO_SWAP_MODE_ID = 0x11333333,
|
||||||
|
AO_MODE_ID = 0x00667329,
|
||||||
|
AO_MODE_ACTIVE_ID = 0x00664339,
|
||||||
|
AUTO_LODBIASADJUST_ID = 0x00638E8F,
|
||||||
|
ICAFE_LOGO_CONFIG_ID = 0x00DB1337,
|
||||||
|
LODBIASADJUST_ID = 0x00738E8F,
|
||||||
|
PRERENDERLIMIT_ID = 0x007BA09E,
|
||||||
|
PS_DYNAMIC_TILING_ID = 0x00E5C6C0,
|
||||||
|
PS_SHADERDISKCACHE_ID = 0x00198FFF,
|
||||||
|
PS_TEXFILTER_ANISO_OPTS2_ID = 0x00E73211,
|
||||||
|
PS_TEXFILTER_BILINEAR_IN_ANISO_ID = 0x0084CD70,
|
||||||
|
PS_TEXFILTER_DISABLE_TRILIN_SLOPE_ID = 0x002ECAF2,
|
||||||
|
PS_TEXFILTER_NO_NEG_LODBIAS_ID = 0x0019BB68,
|
||||||
|
QUALITY_ENHANCEMENTS_ID = 0x00CE2691,
|
||||||
|
REFRESH_RATE_OVERRIDE_ID = 0x0064B541,
|
||||||
|
SET_POWER_THROTTLE_FOR_PCIe_COMPLIANCE_ID = 0x00AE785C,
|
||||||
|
SET_VAB_DATA_ID = 0x00AB8687,
|
||||||
|
VSYNCMODE_ID = 0x00A879CF,
|
||||||
|
VSYNCTEARCONTROL_ID = 0x005A375C,
|
||||||
|
TOTAL_DWORD_SETTING_NUM = 80,
|
||||||
|
TOTAL_WSTRING_SETTING_NUM = 4,
|
||||||
|
TOTAL_SETTING_NUM = 84,
|
||||||
|
INVALID_SETTING_ID = 0xFFFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvShimSetting : uint
|
||||||
|
{
|
||||||
|
SHIM_RENDERING_MODE_INTEGRATED = 0x00000000,
|
||||||
|
SHIM_RENDERING_MODE_ENABLE = 0x00000001,
|
||||||
|
SHIM_RENDERING_MODE_USER_EDITABLE = 0x00000002,
|
||||||
|
SHIM_RENDERING_MODE_MASK = 0x00000003,
|
||||||
|
SHIM_RENDERING_MODE_VIDEO_MASK = 0x00000004,
|
||||||
|
SHIM_RENDERING_MODE_VARYING_BIT = 0x00000008,
|
||||||
|
SHIM_RENDERING_MODE_AUTO_SELECT = 0x00000010,
|
||||||
|
SHIM_RENDERING_MODE_OVERRIDE_BIT = 0x80000000,
|
||||||
|
SHIM_RENDERING_MODE_NUM_VALUES = 8,
|
||||||
|
SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NvThreadControlSetting : uint
|
||||||
|
{
|
||||||
|
OGL_THREAD_CONTROL_ENABLE = 0x00000001,
|
||||||
|
OGL_THREAD_CONTROL_DISABLE = 0x00000002,
|
||||||
|
OGL_THREAD_CONTROL_NUM_VALUES = 2,
|
||||||
|
OGL_THREAD_CONTROL_DEFAULT = 0
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,19 @@ namespace osu.Desktop
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// run Squirrel first, as the app may exit after these run
|
/*
|
||||||
|
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
|
||||||
|
*
|
||||||
|
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
|
||||||
|
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
|
||||||
|
* namely by checking loaded assemblies:
|
||||||
|
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
|
||||||
|
*
|
||||||
|
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
|
||||||
|
* the app will then do completely broken things like:
|
||||||
|
* - not creating system shortcuts (as the logic is if'd out if "running tests")
|
||||||
|
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
|
||||||
|
*/
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
var windowsVersion = Environment.OSVersion.Version;
|
var windowsVersion = Environment.OSVersion.Version;
|
||||||
@ -54,6 +66,11 @@ namespace osu.Desktop
|
|||||||
setupSquirrel();
|
setupSquirrel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NVIDIA profiles are based on the executable name of a process.
|
||||||
|
// Lazer and stable share the same executable name.
|
||||||
|
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
|
||||||
|
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||||
|
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
string cwd = Environment.CurrentDirectory;
|
string cwd = Environment.CurrentDirectory;
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
Mod = new CatchModHidden(),
|
Mod = new CatchModHidden(),
|
||||||
PassCondition = () => Player.Results.Count > 0
|
PassCondition = () => Player.Results.Count > 0
|
||||||
&& Player.ChildrenOfType<DrawableJuiceStream>().Single().Alpha > 0
|
&& Player.ChildrenOfType<DrawableJuiceStream>().Single().Alpha > 0
|
||||||
&& Player.ChildrenOfType<DrawableFruit>().Last().Alpha > 0
|
&& Player.ChildrenOfType<DrawableFruit>().First().Alpha > 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Scoring;
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int difficultyPeppyStars = (int)Math.Round(
|
scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||||
(baseBeatmap.Difficulty.DrainRate
|
|
||||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
|
||||||
+ baseBeatmap.Difficulty.CircleSize
|
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
|
||||||
|
|
||||||
scoreMultiplier = difficultyPeppyStars;
|
|
||||||
|
|
||||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
|
|
||||||
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
AddInternal(bananaContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
|
|
||||||
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
AddInternal(dropletContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both, });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
public partial class NestedFruitContainer : Container
|
||||||
|
{
|
||||||
|
/// <remarks>
|
||||||
|
/// This comparison logic is a copy of <see cref="HitObjectContainer"/> comparison logic,
|
||||||
|
/// which can't be easily extracted to a more common place.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="HitObjectContainer.Compare"/>
|
||||||
|
protected override int Compare(Drawable x, Drawable y)
|
||||||
|
{
|
||||||
|
if (x is not DrawableCatchHitObject xObj || y is not DrawableCatchHitObject yObj)
|
||||||
|
return base.Compare(x, y);
|
||||||
|
|
||||||
|
int result = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
|
||||||
|
return result == 0 ? CompareReverseChildID(x, y) : result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -20,20 +21,73 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
private const int combo_cap = 200;
|
private const int combo_cap = 200;
|
||||||
private const double combo_base = 4;
|
private const double combo_base = 4;
|
||||||
|
|
||||||
|
private double fruitTinyScale;
|
||||||
|
|
||||||
public CatchScoreProcessor()
|
public CatchScoreProcessor()
|
||||||
: base(new CatchRuleset())
|
: base(new CatchRuleset())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Reset(bool storeResults)
|
||||||
|
{
|
||||||
|
base.Reset(storeResults);
|
||||||
|
|
||||||
|
// large ticks are *purposefully* not counted to match stable
|
||||||
|
int fruitTinyScaleDivisor = MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) + MaximumResultCounts.GetValueOrDefault(HitResult.Great);
|
||||||
|
fruitTinyScale = fruitTinyScaleDivisor == 0
|
||||||
|
? 0
|
||||||
|
: (double)MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) / fruitTinyScaleDivisor;
|
||||||
|
}
|
||||||
|
|
||||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||||
{
|
{
|
||||||
return 600000 * comboProgress
|
const int max_tiny_droplets_portion = 400000;
|
||||||
+ 400000 * Accuracy.Value * accuracyProgress
|
|
||||||
|
double comboPortion = 1000000 - max_tiny_droplets_portion + max_tiny_droplets_portion * (1 - fruitTinyScale);
|
||||||
|
double dropletsPortion = max_tiny_droplets_portion * fruitTinyScale;
|
||||||
|
double dropletsHit = MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) == 0
|
||||||
|
? 0
|
||||||
|
: (double)ScoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit) / MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit);
|
||||||
|
|
||||||
|
return comboPortion * comboProgress
|
||||||
|
+ dropletsPortion * dropletsHit
|
||||||
+ bonusPortion;
|
+ bonusPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int GetBaseScoreForResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
// dirty hack to emulate accuracy on stable weighting every object equally in accuracy portion
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return 300;
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetBaseScoreForResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
protected override double GetComboScoreChange(JudgementResult result)
|
protected override double GetComboScoreChange(JudgementResult result)
|
||||||
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
{
|
||||||
|
double baseIncrease = 0;
|
||||||
|
|
||||||
|
switch (result.Type)
|
||||||
|
{
|
||||||
|
case HitResult.Great:
|
||||||
|
baseIncrease = 300;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
baseIncrease = 100;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||||
|
}
|
||||||
|
|
||||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
public override ScoreRank RankFromAccuracy(double accuracy)
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
{
|
{
|
||||||
@ -21,5 +31,51 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderDimsOnlyAfterStartTime()
|
||||||
|
{
|
||||||
|
bool sliderDimmedBeforeStartTime = false;
|
||||||
|
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModFlashlight(),
|
||||||
|
PassCondition = () =>
|
||||||
|
{
|
||||||
|
sliderDimmedBeforeStartTime |=
|
||||||
|
Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>().Single().FlashlightDim > 0;
|
||||||
|
return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime;
|
||||||
|
},
|
||||||
|
Beatmap = new OsuBeatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, },
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(100))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
StackLeniency = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(990, new Vector2()),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2001, new Vector2(100)),
|
||||||
|
},
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int difficultyPeppyStars = (int)Math.Round(
|
scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||||
(baseBeatmap.Difficulty.DrainRate
|
|
||||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
|
||||||
+ baseBeatmap.Difficulty.CircleSize
|
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
|
||||||
|
|
||||||
scoreMultiplier = difficultyPeppyStars;
|
|
||||||
|
|
||||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||||
|
|
||||||
|
@ -4,20 +4,15 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Primitives;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -41,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public Action<DragEvent> DragInProgress;
|
public Action<DragEvent> DragInProgress;
|
||||||
public Action DragEnded;
|
public Action DragEnded;
|
||||||
|
|
||||||
public List<PathControlPoint> PointsInSegment;
|
|
||||||
|
|
||||||
public readonly BindableBool IsSelected = new BindableBool();
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
public readonly PathControlPoint ControlPoint;
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
@ -56,27 +49,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private IBindable<Vector2> hitObjectPosition;
|
private IBindable<Vector2> hitObjectPosition;
|
||||||
private IBindable<float> hitObjectScale;
|
private IBindable<float> hitObjectScale;
|
||||||
|
|
||||||
[UsedImplicitly]
|
|
||||||
private readonly IBindable<int> hitObjectVersion;
|
|
||||||
|
|
||||||
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
|
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.hitObject = hitObject;
|
||||||
ControlPoint = controlPoint;
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
// we don't want to run the path type update on construction as it may inadvertently change the hit object.
|
|
||||||
cachePoints(hitObject);
|
|
||||||
|
|
||||||
hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
|
|
||||||
|
|
||||||
// schedule ensure that updates are only applied after all operations from a single frame are applied.
|
|
||||||
// this avoids inadvertently changing the hit object path type for batch operations.
|
|
||||||
hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
|
|
||||||
{
|
|
||||||
cachePoints(hitObject);
|
|
||||||
updatePathType();
|
|
||||||
}));
|
|
||||||
|
|
||||||
controlPoint.Changed += updateMarkerDisplay;
|
controlPoint.Changed += updateMarkerDisplay;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -214,28 +191,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
|
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
|
||||||
|
|
||||||
private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles correction of invalid path types.
|
|
||||||
/// </summary>
|
|
||||||
private void updatePathType()
|
|
||||||
{
|
|
||||||
if (ControlPoint.Type != PathType.PERFECT_CURVE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (PointsInSegment.Count > 3)
|
|
||||||
ControlPoint.Type = PathType.BEZIER;
|
|
||||||
|
|
||||||
if (PointsInSegment.Count != 3)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
|
|
||||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
|
||||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
|
||||||
ControlPoint.Type = PathType.BEZIER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the state of the circular control point marker.
|
/// Updates the state of the circular control point marker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -14,10 +14,12 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -76,6 +78,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles correction of invalid path types.
|
||||||
|
/// </summary>
|
||||||
|
public void EnsureValidPathTypes()
|
||||||
|
{
|
||||||
|
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
|
||||||
|
|
||||||
|
foreach (var controlPoint in controlPoints)
|
||||||
|
{
|
||||||
|
if (controlPoint.Type != null)
|
||||||
|
{
|
||||||
|
pointsInCurrentSegment.Add(controlPoint);
|
||||||
|
ensureValidPathType(pointsInCurrentSegment);
|
||||||
|
pointsInCurrentSegment.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pointsInCurrentSegment.Add(controlPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureValidPathType(pointsInCurrentSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureValidPathType(IReadOnlyList<PathControlPoint> segment)
|
||||||
|
{
|
||||||
|
if (segment.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var first = segment[0];
|
||||||
|
|
||||||
|
if (first.Type != PathType.PERFECT_CURVE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (segment.Count > 3)
|
||||||
|
first.Type = PathType.BEZIER;
|
||||||
|
|
||||||
|
if (segment.Count != 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
|
||||||
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||||
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
|
first.Type = PathType.BEZIER;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
|
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
|
||||||
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
|
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
|
||||||
@ -240,7 +286,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||||
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
||||||
{
|
{
|
||||||
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
|
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
|
||||||
|
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
|
||||||
|
|
||||||
if (type?.Type == SplineType.PerfectCurve)
|
if (type?.Type == SplineType.PerfectCurve)
|
||||||
{
|
{
|
||||||
@ -249,8 +296,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
// and one segment of the previous type.
|
// and one segment of the previous type.
|
||||||
int thirdPointIndex = indexInSegment + 2;
|
int thirdPointIndex = indexInSegment + 2;
|
||||||
|
|
||||||
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
|
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||||
piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
|
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
hitObject.Path.ExpectedDistance.Value = null;
|
hitObject.Path.ExpectedDistance.Value = null;
|
||||||
@ -339,6 +386,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
||||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||||
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
|
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
|
||||||
|
|
||||||
|
EnsureValidPathTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DragEnded() => changeHandler?.EndChange();
|
public void DragEnded() => changeHandler?.EndChange();
|
||||||
@ -412,6 +461,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
updatePathType(p, type);
|
updatePathType(p, type);
|
||||||
|
|
||||||
|
EnsureValidPathTypes();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (countOfState == totalCount)
|
if (countOfState == totalCount)
|
||||||
|
@ -267,6 +267,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
segmentStart.Type = PathType.BEZIER;
|
segmentStart.Type = PathType.BEZIER;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controlPointVisualiser.EnsureValidPathTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCursor()
|
private void updateCursor()
|
||||||
|
@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
// Move the control points from the insertion index onwards to make room for the insertion
|
||||||
controlPoints.Insert(insertionIndex, pathControlPoint);
|
controlPoints.Insert(insertionIndex, pathControlPoint);
|
||||||
|
|
||||||
|
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||||
|
|
||||||
HitObject.SnapTo(distanceSnapProvider);
|
HitObject.SnapTo(distanceSnapProvider);
|
||||||
|
|
||||||
return pathControlPoint;
|
return pathControlPoint;
|
||||||
@ -275,6 +277,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
controlPoints.Remove(c);
|
controlPoints.Remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||||
|
|
||||||
// Snap the slider to the current beat divisor before checking length validity.
|
// Snap the slider to the current beat divisor before checking length validity.
|
||||||
HitObject.SnapTo(distanceSnapProvider);
|
HitObject.SnapTo(distanceSnapProvider);
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
if (drawable is DrawableSlider s)
|
if (drawable is DrawableSlider s)
|
||||||
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
s.Tracking.ValueChanged += _ => flashlight.OnSliderTrackingChange(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
||||||
@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
FlashlightSmoothness = 1.4f;
|
FlashlightSmoothness = 1.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
public void OnSliderTrackingChange(DrawableSlider e)
|
||||||
{
|
{
|
||||||
// If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield.
|
// If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield.
|
||||||
FlashlightDim = e.NewValue ? 0.8f : 0.0f;
|
FlashlightDim = Time.Current >= e.HitObject.StartTime && e.Tracking.Value ? 0.8f : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Layout;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -66,6 +67,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private Container<DrawableSliderRepeat> repeatContainer;
|
private Container<DrawableSliderRepeat> repeatContainer;
|
||||||
private PausableSkinnableSound slidingSample;
|
private PausableSkinnableSound slidingSample;
|
||||||
|
|
||||||
|
private readonly LayoutValue drawSizeLayout;
|
||||||
|
|
||||||
public DrawableSlider()
|
public DrawableSlider()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -82,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
};
|
};
|
||||||
|
AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -246,21 +250,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Ball.UpdateProgress(completionProgress);
|
Ball.UpdateProgress(completionProgress);
|
||||||
SliderBody?.UpdateProgress(HeadCircle.IsHit ? completionProgress : 0);
|
SliderBody?.UpdateProgress(HeadCircle.IsHit ? completionProgress : 0);
|
||||||
|
|
||||||
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
foreach (DrawableSliderRepeat repeat in repeatContainer)
|
||||||
{
|
repeat.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0));
|
||||||
if (hitObject is ITrackSnaking s)
|
|
||||||
s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
Size = SliderBody?.Size ?? Vector2.Zero;
|
Size = SliderBody?.Size ?? Vector2.Zero;
|
||||||
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
|
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
|
||||||
|
|
||||||
if (DrawSize != Vector2.Zero)
|
if (!drawSizeLayout.IsValid)
|
||||||
{
|
{
|
||||||
var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize);
|
Vector2 pos = Vector2.Divide(OriginPosition, DrawSize);
|
||||||
foreach (var obj in NestedHitObjects)
|
foreach (var obj in NestedHitObjects)
|
||||||
obj.RelativeAnchorPosition = childAnchorPosition;
|
obj.RelativeAnchorPosition = pos;
|
||||||
Ball.RelativeAnchorPosition = childAnchorPosition;
|
Ball.RelativeAnchorPosition = pos;
|
||||||
|
|
||||||
|
drawSizeLayout.Validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public partial class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
|
public partial class DrawableSliderRepeat : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
// 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 osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A component which tracks the current end snaking position of a slider.
|
|
||||||
/// </summary>
|
|
||||||
public interface ITrackSnaking
|
|
||||||
{
|
|
||||||
void UpdateSnakingPosition(Vector2 start, Vector2 end);
|
|
||||||
}
|
|
||||||
}
|
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
InnerRadius = arc_radius,
|
InnerRadius = arc_radius,
|
||||||
RoundedCaps = true,
|
RoundedCaps = true,
|
||||||
GlowColour = new Color4(171, 255, 255, 255)
|
GlowColour = new Color4(171, 255, 255, 180)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
@ -65,11 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
difficultyPeppyStars = (int)Math.Round(
|
difficultyPeppyStars = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||||
(baseBeatmap.Difficulty.DrainRate
|
|
||||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
|
||||||
+ baseBeatmap.Difficulty.CircleSize
|
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
|
||||||
|
|
||||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("post message", () => channelManager.PostMessage("Something interesting"));
|
AddStep("post message", () => channelManager.PostMessage("Something interesting"));
|
||||||
AddUntilStep("message postesd", () => !channel.Messages.Any(m => m is LocalMessage));
|
AddUntilStep("message posted", () => !channel.Messages.Any(m => m is LocalMessage));
|
||||||
|
|
||||||
AddStep("post /help command", () => channelManager.PostCommand("help", channel));
|
AddStep("post /help command", () => channelManager.PostCommand("help", channel));
|
||||||
AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
|
AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
|
||||||
@ -146,6 +146,23 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddAssert("channel has no more messages", () => channel.Messages, () => Is.Empty);
|
AddAssert("channel has no more messages", () => channel.Messages, () => Is.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCommandNameCaseInsensitivity()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("post /me command", () => channelManager.PostCommand("ME DANCES"));
|
||||||
|
AddUntilStep("/me command received", () => channel.Messages.Last().Content.Contains("DANCES"));
|
||||||
|
AddStep("post /help command", () => channelManager.PostCommand("HeLp"));
|
||||||
|
AddUntilStep("/help command received", () => channel.Messages.Last().Content.Contains("Supported commands"));
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePostMessageRequest(PostMessageRequest request)
|
private void handlePostMessageRequest(PostMessageRequest request)
|
||||||
{
|
{
|
||||||
var message = new Message(++currentMessageId)
|
var message = new Message(++currentMessageId)
|
||||||
|
@ -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.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -182,9 +183,63 @@ namespace osu.Game.Tests.Database
|
|||||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002));
|
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomRulesetScoreNotSubjectToUpgrades([Values] bool available)
|
||||||
|
{
|
||||||
|
RulesetInfo rulesetInfo = null!;
|
||||||
|
ScoreInfo scoreInfo = null!;
|
||||||
|
TestBackgroundDataStoreProcessor processor = null!;
|
||||||
|
|
||||||
|
AddStep("Add unavailable ruleset", () => Realm.Write(r => r.Add(rulesetInfo = new RulesetInfo
|
||||||
|
{
|
||||||
|
ShortName = Guid.NewGuid().ToString(),
|
||||||
|
Available = available
|
||||||
|
})));
|
||||||
|
|
||||||
|
AddStep("Add score for unavailable ruleset", () => Realm.Write(r => r.Add(scoreInfo = new ScoreInfo(
|
||||||
|
ruleset: rulesetInfo,
|
||||||
|
beatmap: r.All<BeatmapInfo>().First())
|
||||||
|
{
|
||||||
|
TotalScoreVersion = 30000001
|
||||||
|
})));
|
||||||
|
|
||||||
|
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||||
|
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||||
|
|
||||||
|
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||||
|
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonLegacyScoreNotSubjectToUpgrades()
|
||||||
|
{
|
||||||
|
ScoreInfo scoreInfo = null!;
|
||||||
|
TestBackgroundDataStoreProcessor processor = null!;
|
||||||
|
|
||||||
|
AddStep("Add score which requires upgrade (and has beatmap)", () =>
|
||||||
|
{
|
||||||
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
r.Add(scoreInfo = new ScoreInfo(ruleset: r.All<RulesetInfo>().First(), beatmap: r.All<BeatmapInfo>().First())
|
||||||
|
{
|
||||||
|
TotalScoreVersion = 30000005,
|
||||||
|
LegacyTotalScore = 123456,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||||
|
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||||
|
|
||||||
|
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||||
|
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005));
|
||||||
|
}
|
||||||
|
|
||||||
public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor
|
public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor
|
||||||
{
|
{
|
||||||
protected override int TimeToSleepDuringGameplay => 10;
|
protected override int TimeToSleepDuringGameplay => 10;
|
||||||
|
|
||||||
|
public bool Completed => ProcessingTask.IsCompleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,6 +310,26 @@ namespace osu.Game.Tests.Mods
|
|||||||
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFormatScoreMultiplier()
|
||||||
|
{
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0).ToString(), "1.00x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x");
|
||||||
|
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9).ToString(), "0.90x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x");
|
||||||
|
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.1).ToString(), "1.10x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x");
|
||||||
|
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.045).ToString(), "1.05x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.05).ToString(), "1.05x");
|
||||||
|
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -339,6 +359,16 @@ namespace osu.Game.Tests.Mods
|
|||||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EditableMod : Mod
|
||||||
|
{
|
||||||
|
public override string Name => string.Empty;
|
||||||
|
public override LocalisableString Description => string.Empty;
|
||||||
|
public override string Acronym => string.Empty;
|
||||||
|
public override double ScoreMultiplier => Multiplier;
|
||||||
|
|
||||||
|
public double Multiplier = 1;
|
||||||
|
}
|
||||||
|
|
||||||
public interface IModCompatibilitySpecification
|
public interface IModCompatibilitySpecification
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
3
osu.Game.Tests/Resources/mania-skin-broken-array.ini
Normal file
3
osu.Game.Tests/Resources/mania-skin-broken-array.ini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[Mania]
|
||||||
|
Keys: 4
|
||||||
|
ColumnLineWidth: 3,,3,3,3
|
@ -114,5 +114,25 @@ namespace osu.Game.Tests.Skins
|
|||||||
Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16));
|
Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestParseArrayWithSomeEmptyElements()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyManiaSkinDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("mania-skin-broken-array.ini"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var configs = decoder.Decode(stream);
|
||||||
|
|
||||||
|
Assert.That(configs.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(configs[0].ColumnLineWidth.Length, Is.EqualTo(5));
|
||||||
|
Assert.That(configs[0].ColumnLineWidth[0], Is.EqualTo(3));
|
||||||
|
Assert.That(configs[0].ColumnLineWidth[1], Is.EqualTo(0)); // malformed entry, should be parsed as zero
|
||||||
|
Assert.That(configs[0].ColumnLineWidth[2], Is.EqualTo(3));
|
||||||
|
Assert.That(configs[0].ColumnLineWidth[3], Is.EqualTo(3));
|
||||||
|
Assert.That(configs[0].ColumnLineWidth[4], Is.EqualTo(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
if (sameRuleset)
|
if (sameRuleset)
|
||||||
{
|
{
|
||||||
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
|
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
|
||||||
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
|
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog)?.PerformOkAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure editor loader didn't resume.
|
// ensure editor loader didn't resume.
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
@ -159,5 +160,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Type = HitResult.Perfect
|
Type = HitResult.Perfect
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSimulateDrain()
|
||||||
|
{
|
||||||
|
ScheduledDelegate del = null!;
|
||||||
|
|
||||||
|
AddStep("simulate drain", () => del = Scheduler.AddDelayed(() => healthProcessor.Health.Value -= 0.00025f * Time.Elapsed, 0, true));
|
||||||
|
AddUntilStep("wait until zero", () => healthProcessor.Health.Value == 0);
|
||||||
|
AddStep("cancel drain", () => del.Cancel());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private readonly Bindable<bool> showHealth = new Bindable<bool>();
|
private readonly Bindable<bool> showHealth = new Bindable<bool>();
|
||||||
|
|
||||||
|
private HealthProcessor healthProcessor;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddStep("create layer", () =>
|
AddStep("create layer", () =>
|
||||||
{
|
{
|
||||||
Child = new HealthProcessorContainer(healthProcessor)
|
Child = new HealthProcessorContainer(this.healthProcessor = healthProcessor)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = layer = new FailingLayer()
|
Child = layer = new FailingLayer()
|
||||||
@ -50,12 +52,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
|
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
|
||||||
{
|
{
|
||||||
if (layer != null)
|
if (layer != null)
|
||||||
layer.Current.Value = val;
|
healthProcessor.Health.Value = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
|
||||||
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
|
AddUntilStep("layer fade is visible", () => layer.ChildrenOfType<Container>().First().Alpha > 0.1f);
|
||||||
AddStep("set health to 1", () => layer.Current.Value = 1f);
|
AddStep("set health to 1", () => healthProcessor.Health.Value = 1f);
|
||||||
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
|
AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType<Container>().First().IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
create(new DrainingHealthProcessor(0));
|
create(new DrainingHealthProcessor(0));
|
||||||
AddUntilStep("layer is visible", () => layer.IsPresent);
|
AddUntilStep("layer is visible", () => layer.IsPresent);
|
||||||
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
||||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
|
||||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
create(new AccumulatingHealthProcessor(1));
|
create(new AccumulatingHealthProcessor(1));
|
||||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
|
||||||
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
AddUntilStep("layer is not visible", () => !layer.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestLayerVisibilityWithDrainingProcessor()
|
public void TestLayerVisibilityWithDrainingProcessor()
|
||||||
{
|
{
|
||||||
create(new DrainingHealthProcessor(0));
|
create(new DrainingHealthProcessor(0));
|
||||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
|
||||||
AddWaitStep("wait for potential fade", 10);
|
AddWaitStep("wait for potential fade", 10);
|
||||||
AddAssert("layer is still visible", () => layer.IsPresent);
|
AddAssert("layer is still visible", () => layer.IsPresent);
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
create(new DrainingHealthProcessor(0));
|
create(new DrainingHealthProcessor(0));
|
||||||
|
|
||||||
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
|
AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1);
|
||||||
|
|
||||||
AddStep("don't show health", () => showHealth.Value = false);
|
AddStep("don't show health", () => showHealth.Value = false);
|
||||||
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached(typeof(HealthProcessor))]
|
[Cached(typeof(HealthProcessor))]
|
||||||
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||||
|
|
||||||
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f };
|
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 600, UseRelativeSize = { Value = false } };
|
||||||
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
|
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
|
||||||
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
|
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
|
||||||
|
|
||||||
@ -35,6 +35,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
healthProcessor.Health.Value -= 0.0001f * Time.Elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHealthDisplayIncrementing()
|
public void TestHealthDisplayIncrementing()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public partial class TestSceneIntroMusicActionHandling : OsuGameTestScene
|
||||||
|
{
|
||||||
|
private GlobalActionContainer globalActionContainer => Game.ChildrenOfType<GlobalActionContainer>().First();
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
CreateNewGame();
|
||||||
|
// we do not want to progress to main menu immediately, hence the override and lack of `ConfirmAtMainMenu()` call here.
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPauseDuringIntro()
|
||||||
|
{
|
||||||
|
AddUntilStep("Wait for music", () => Game?.MusicController.IsPlaying == true);
|
||||||
|
|
||||||
|
// Check that pause doesn't work during intro sequence.
|
||||||
|
AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||||
|
AddAssert("Still playing before menu", () => Game?.MusicController.IsPlaying == true);
|
||||||
|
AddUntilStep("Wait for main menu", () => Game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
|
||||||
|
|
||||||
|
// Check that toggling after intro still works.
|
||||||
|
AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||||
|
AddUntilStep("Music paused", () => Game?.MusicController.IsPlaying == false && Game?.MusicController.UserPauseRequested == true);
|
||||||
|
AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||||
|
AddUntilStep("Music resumed", () => Game?.MusicController.IsPlaying == true && Game?.MusicController.UserPauseRequested == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,12 +10,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
using osu.Game.Screens.OnlinePlay;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene
|
public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene
|
||||||
{
|
{
|
||||||
private FreeModSelectOverlay freeModSelectOverlay;
|
private FreeModSelectOverlay freeModSelectOverlay;
|
||||||
|
private FooterButtonFreeMods footerButtonFreeMods;
|
||||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -119,11 +122,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectAllViaFooterButtonThenDeselectFromOverlay()
|
||||||
|
{
|
||||||
|
createFreeModSelect();
|
||||||
|
|
||||||
|
AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||||
|
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||||
|
|
||||||
|
AddStep("click footer select all button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(footerButtonFreeMods);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
|
||||||
|
AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "all"));
|
||||||
|
|
||||||
|
AddStep("click deselect all button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
|
||||||
|
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||||
|
}
|
||||||
|
|
||||||
private void createFreeModSelect()
|
private void createFreeModSelect()
|
||||||
{
|
{
|
||||||
AddStep("create free mod select screen", () => Child = freeModSelectOverlay = new FreeModSelectOverlay
|
AddStep("create free mod select screen", () => Children = new Drawable[]
|
||||||
{
|
{
|
||||||
State = { Value = Visibility.Visible }
|
freeModSelectOverlay = new FreeModSelectOverlay
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
},
|
||||||
|
footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
AddUntilStep("all column content loaded",
|
AddUntilStep("all column content loaded",
|
||||||
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||||
@ -134,10 +172,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
var allAvailableMods = availableMods.Value
|
var allAvailableMods = availableMods.Value
|
||||||
.Where(pair => pair.Key != ModType.System)
|
.Where(pair => pair.Key != ModType.System)
|
||||||
.SelectMany(pair => pair.Value)
|
.SelectMany(pair => ModUtils.FlattenMods(pair.Value))
|
||||||
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
if (freeModSelectOverlay.SelectedMods.Value.Count != allAvailableMods.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
foreach (var availableMod in allAvailableMods)
|
foreach (var availableMod in allAvailableMods)
|
||||||
{
|
{
|
||||||
if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
|
if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
|
||||||
|
@ -29,6 +29,7 @@ using osu.Game.Rulesets.Catch;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||||
@ -690,6 +691,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
|
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
|
||||||
|
|
||||||
|
AddAssert("check is fail", () =>
|
||||||
|
{
|
||||||
|
var scoreInfo = ((ResultsScreen)multiplayerComponents.CurrentScreen).Score;
|
||||||
|
|
||||||
|
return !scoreInfo.Passed && scoreInfo.Rank == ScoreRank.F;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -799,11 +799,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("attempt exit", () =>
|
AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2);
|
||||||
{
|
|
||||||
for (int i = 0; i < 2; ++i)
|
|
||||||
Game.ScreenStack.CurrentScreen.Exit();
|
|
||||||
});
|
|
||||||
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,6 +938,35 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf<ModTouchDevice>());
|
AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf<ModTouchDevice>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExitSongSelectAndImmediatelyClickLogo()
|
||||||
|
{
|
||||||
|
Screens.Select.SongSelect songSelect = null;
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||||
|
|
||||||
|
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||||
|
|
||||||
|
AddStep("press escape and then click logo immediately", () =>
|
||||||
|
{
|
||||||
|
InputManager.Key(Key.Escape);
|
||||||
|
clickLogoWhenNotCurrent();
|
||||||
|
});
|
||||||
|
|
||||||
|
void clickLogoWhenNotCurrent()
|
||||||
|
{
|
||||||
|
if (songSelect.IsCurrentScreen())
|
||||||
|
Scheduler.AddOnce(clickLogoWhenNotCurrent);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(Game.ChildrenOfType<OsuLogo>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Func<Player> playToResults()
|
private Func<Player> playToResults()
|
||||||
{
|
{
|
||||||
var player = playToCompletion();
|
var player = playToCompletion();
|
||||||
|
@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
var cardContainer = this.ChildrenOfType<ReverseChildIDFillFlowContainer<BeatmapCard>>().Single().Parent;
|
var cardContainer = this.ChildrenOfType<ReverseChildIDFillFlowContainer<BeatmapCard>>().Single().Parent;
|
||||||
var expandedContent = this.ChildrenOfType<ExpandedContentScrollContainer>().Single();
|
var expandedContent = this.ChildrenOfType<ExpandedContentScrollContainer>().Single();
|
||||||
return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer.ScreenSpaceDrawQuad.Contains(v));
|
return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer!.ScreenSpaceDrawQuad.Contains(v));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +268,26 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert("update not received", () => update == null);
|
AddAssert("update not received", () => update == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed()
|
||||||
|
{
|
||||||
|
int userId = getUserId();
|
||||||
|
long scoreId = getScoreId();
|
||||||
|
setUpUser(userId);
|
||||||
|
|
||||||
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
|
SoloStatisticsUpdate? update = null;
|
||||||
|
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||||
|
|
||||||
|
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||||
|
|
||||||
|
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||||
|
AddUntilStep("update received", () => update != null);
|
||||||
|
AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
|
||||||
|
AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||||
|
}
|
||||||
|
|
||||||
private int nextUserId = 2000;
|
private int nextUserId = 2000;
|
||||||
private long nextScoreId = 50000;
|
private long nextScoreId = 50000;
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<APIWikiPage> wikiPageData = new Bindable<APIWikiPage>(new APIWikiPage
|
private readonly Bindable<APIWikiPage> wikiPageData = new Bindable<APIWikiPage>(new APIWikiPage
|
||||||
{
|
{
|
||||||
Title = "Main Page",
|
Title = "Main page",
|
||||||
Path = "Main_Page",
|
Path = WikiOverlay.INDEX_PATH,
|
||||||
});
|
});
|
||||||
|
|
||||||
private TestHeader header;
|
private TestHeader header;
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
|
// From https://osu.ppy.sh/api/v2/wiki/en/Main_page
|
||||||
private const string main_page_markdown =
|
private const string main_page_markdown =
|
||||||
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
|
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
|
||||||
}
|
}
|
||||||
|
@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/");
|
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/");
|
||||||
|
|
||||||
AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)");
|
AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_page");
|
||||||
|
|
||||||
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
||||||
@ -275,7 +275,7 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed
|
|||||||
AddStep("set content", () =>
|
AddStep("set content", () =>
|
||||||
{
|
{
|
||||||
markdownContainer.Text = @"
|
markdownContainer.Text = @"
|
||||||
This is a paragraph containing `inline code` synatax.
|
This is a paragraph containing `inline code` syntax.
|
||||||
Oh wow I do love the `WikiMarkdownContainer`, it is very cool!
|
Oh wow I do love the `WikiMarkdownContainer`, it is very cool!
|
||||||
|
|
||||||
This is a line before the fenced code block:
|
This is a line before the fenced code block:
|
||||||
|
@ -107,12 +107,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
|
// From https://osu.ppy.sh/api/v2/wiki/en/Main_page
|
||||||
private APIWikiPage responseMainPage => new APIWikiPage
|
private APIWikiPage responseMainPage => new APIWikiPage
|
||||||
{
|
{
|
||||||
Title = "Main Page",
|
Title = "Main page",
|
||||||
Layout = "main_page",
|
Layout = WikiOverlay.INDEX_PATH.ToLowerInvariant(), // custom classes are always lower snake.
|
||||||
Path = "Main_Page",
|
Path = WikiOverlay.INDEX_PATH,
|
||||||
Locale = "en",
|
Locale = "en",
|
||||||
Subtitle = null,
|
Subtitle = null,
|
||||||
Markdown =
|
Markdown =
|
||||||
|
@ -9,6 +9,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private bool assertModsMultiplier(IEnumerable<Mod> mods)
|
private bool assertModsMultiplier(IEnumerable<Mod> mods)
|
||||||
{
|
{
|
||||||
double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
|
double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
|
||||||
string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
|
string expectedValue = multiplier == 1 ? string.Empty : ModUtils.FormatScoreMultiplier(multiplier).ToString();
|
||||||
|
|
||||||
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
@ -28,6 +29,8 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class BackgroundDataStoreProcessor : Component
|
public partial class BackgroundDataStoreProcessor : Component
|
||||||
{
|
{
|
||||||
|
protected Task ProcessingTask { get; private set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesetStore { get; set; } = null!;
|
private RulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
ProcessingTask = Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
Logger.Log("Beginning background data store processing..");
|
Logger.Log("Beginning background data store processing..");
|
||||||
|
|
||||||
@ -314,10 +317,17 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
Logger.Log("Querying for scores that need total score conversion...");
|
Logger.Log("Querying for scores that need total score conversion...");
|
||||||
|
|
||||||
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(r.All<ScoreInfo>()
|
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(
|
||||||
.Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null
|
r.All<ScoreInfo>()
|
||||||
&& s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION)
|
.Where(s => !s.BackgroundReprocessingFailed
|
||||||
.AsEnumerable().Select(s => s.ID)));
|
&& s.BeatmapInfo != null
|
||||||
|
&& s.IsLegacyScore
|
||||||
|
&& s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION)
|
||||||
|
.AsEnumerable()
|
||||||
|
// must be done after materialisation, as realm doesn't want to support
|
||||||
|
// nested property predicates
|
||||||
|
.Where(s => s.Ruleset.IsLegacyRuleset())
|
||||||
|
.Select(s => s.ID)));
|
||||||
|
|
||||||
Logger.Log($"Found {scoreIds.Count} scores which require total score conversion.");
|
Logger.Log($"Found {scoreIds.Count} scores which require total score conversion.");
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -193,6 +194,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
||||||
|
|
||||||
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
|
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
|
||||||
|
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
||||||
@ -420,5 +422,6 @@ namespace osu.Game.Configuration
|
|||||||
EditorShowSpeedChanges,
|
EditorShowSpeedChanges,
|
||||||
TouchDisableGameplayTaps,
|
TouchDisableGameplayTaps,
|
||||||
ModSelectTextSearchStartsActive,
|
ModSelectTextSearchStartsActive,
|
||||||
|
UserOnlineStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// 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;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -98,15 +98,11 @@ namespace osu.Game.Database
|
|||||||
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
||||||
realm.Write(r =>
|
realm.Write(r =>
|
||||||
{
|
{
|
||||||
// TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707)
|
foreach (var file in r.All<RealmFile>().Filter(@$"{nameof(RealmFile.Usages)}.@count = 0"))
|
||||||
var files = r.All<RealmFile>().ToList();
|
|
||||||
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
{
|
||||||
totalFiles++;
|
totalFiles++;
|
||||||
|
|
||||||
if (file.BacklinksCount > 0)
|
Debug.Assert(file.BacklinksCount == 0);
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -311,13 +311,22 @@ namespace osu.Game.Database
|
|||||||
long maximumLegacyBonusScore = attributes.BonusScore;
|
long maximumLegacyBonusScore = attributes.BonusScore;
|
||||||
|
|
||||||
double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy;
|
double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy;
|
||||||
// We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio.
|
|
||||||
// Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0
|
double comboProportion;
|
||||||
// when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot).
|
|
||||||
// In such cases, just assume 0.
|
if (maximumLegacyComboScore + maximumLegacyBonusScore > 0)
|
||||||
double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0
|
{
|
||||||
? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore)
|
// We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio.
|
||||||
: 0;
|
comboProportion = Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Two possible causes:
|
||||||
|
// the beatmap has no bonus objects *AND*
|
||||||
|
// either the active mods have a zero mod multiplier, in which case assume 0,
|
||||||
|
// or the *beatmap* has a zero `difficultyPeppyStars` (or just no combo-giving objects), in which case assume 1.
|
||||||
|
comboProportion = legacyModMultiplier == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
|
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
|
||||||
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
|
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
|
||||||
@ -437,16 +446,42 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
|
// compare logic in `CatchScoreProcessor`.
|
||||||
|
|
||||||
|
// this could technically be slightly incorrect in the case of stable scores.
|
||||||
|
// because large droplet misses are counted as full misses in stable scores,
|
||||||
|
// `score.MaximumStatistics.GetValueOrDefault(Great)` will be equal to the count of fruits *and* large droplets
|
||||||
|
// rather than just fruits (which was the intent).
|
||||||
|
// this is not fixable without introducing an extra legacy score attribute dedicated for catch,
|
||||||
|
// and this is a ballpark conversion process anyway, so attempt to trudge on.
|
||||||
|
int fruitTinyScaleDivisor = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + score.MaximumStatistics.GetValueOrDefault(HitResult.Great);
|
||||||
|
double fruitTinyScale = fruitTinyScaleDivisor == 0
|
||||||
|
? 0
|
||||||
|
: (double)score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) / fruitTinyScaleDivisor;
|
||||||
|
|
||||||
|
const int max_tiny_droplets_portion = 400000;
|
||||||
|
|
||||||
|
double comboPortion = 1000000 - max_tiny_droplets_portion + max_tiny_droplets_portion * (1 - fruitTinyScale);
|
||||||
|
double dropletsPortion = max_tiny_droplets_portion * fruitTinyScale;
|
||||||
|
double dropletsHit = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) == 0
|
||||||
|
? 0
|
||||||
|
: (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit);
|
||||||
|
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScore = (long)Math.Round((
|
||||||
600000 * comboProportion
|
comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss))
|
||||||
+ 400000 * score.Accuracy
|
+ dropletsPortion * dropletsHit
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion) * modMultiplier);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
|
// in the mania case accuracy actually changes between score V1 and score V2 / standardised
|
||||||
|
// (PERFECT weighting changes from 300 to 305),
|
||||||
|
// so for better accuracy recompute accuracy locally based on hit statistics and use that instead,
|
||||||
|
double scoreV2Accuracy = ComputeAccuracy(score);
|
||||||
|
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScore = (long)Math.Round((
|
||||||
850000 * comboProportion
|
850000 * comboProportion
|
||||||
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
|
+ 150000 * Math.Pow(scoreV2Accuracy, 2 + 2 * scoreV2Accuracy)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion) * modMultiplier);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -461,6 +496,94 @@ namespace osu.Game.Database
|
|||||||
return convertedTotalScore;
|
return convertedTotalScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// For catch, the general method of calculating the combo proportion used for other rulesets is generally useless.
|
||||||
|
/// This is because in stable score V1, catch has quadratic score progression,
|
||||||
|
/// while in stable score V2, score progression is logarithmic up to 200 combo and then linear.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This means that applying the naive rescale method to scores with lots of short combos (think 10x 100-long combos on a 1000-object map)
|
||||||
|
/// by linearly rescaling the combo portion as given by score V1 leads to horribly underestimating it.
|
||||||
|
/// Therefore this method attempts to counteract this by calculating the best case estimate for the combo proportion that takes all of the above into account.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The general idea is that aside from the <paramref name="scoreMaxCombo"/> which the player is known to have hit,
|
||||||
|
/// the remaining misses are evenly distributed across the rest of the objects that give combo.
|
||||||
|
/// This is therefore a worst-case estimate.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
private static double estimateComboProportionForCatch(int beatmapMaxCombo, int scoreMaxCombo, int scoreMissCount)
|
||||||
|
{
|
||||||
|
if (beatmapMaxCombo == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (scoreMaxCombo == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (beatmapMaxCombo == scoreMaxCombo)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
double estimatedBestCaseTotal = estimateBestCaseComboTotal(beatmapMaxCombo);
|
||||||
|
|
||||||
|
int remainingCombo = beatmapMaxCombo - (scoreMaxCombo + scoreMissCount);
|
||||||
|
double totalDroppedScore = 0;
|
||||||
|
|
||||||
|
int assumedLengthOfRemainingCombos = (int)Math.Floor((double)remainingCombo / scoreMissCount);
|
||||||
|
|
||||||
|
if (assumedLengthOfRemainingCombos > 0)
|
||||||
|
{
|
||||||
|
int assumedCombosCount = (int)Math.Floor((double)remainingCombo / assumedLengthOfRemainingCombos);
|
||||||
|
totalDroppedScore += assumedCombosCount * estimateDroppedComboScoreAfterMiss(assumedLengthOfRemainingCombos);
|
||||||
|
|
||||||
|
remainingCombo -= assumedCombosCount * assumedLengthOfRemainingCombos;
|
||||||
|
|
||||||
|
if (remainingCombo > 0)
|
||||||
|
totalDroppedScore += estimateDroppedComboScoreAfterMiss(remainingCombo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// there are so many misses that attempting to evenly divide remaining combo results in 0 length per combo,
|
||||||
|
// i.e. all remaining judgements are combo breaks.
|
||||||
|
// in that case, presume every single remaining object is a miss and did not give any combo score.
|
||||||
|
totalDroppedScore = estimatedBestCaseTotal - estimateBestCaseComboTotal(scoreMaxCombo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return estimatedBestCaseTotal == 0
|
||||||
|
? 1
|
||||||
|
: 1 - Math.Clamp(totalDroppedScore / estimatedBestCaseTotal, 0, 1);
|
||||||
|
|
||||||
|
double estimateBestCaseComboTotal(int maxCombo)
|
||||||
|
{
|
||||||
|
if (maxCombo == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
double estimatedTotal = 0.5 * Math.Min(maxCombo, 2);
|
||||||
|
|
||||||
|
if (maxCombo <= 2)
|
||||||
|
return estimatedTotal;
|
||||||
|
|
||||||
|
// int_2^x log_4(t) dt
|
||||||
|
estimatedTotal += (Math.Min(maxCombo, 200) * (Math.Log(Math.Min(maxCombo, 200)) - 1) + 2 - Math.Log(4)) / Math.Log(4);
|
||||||
|
|
||||||
|
if (maxCombo <= 200)
|
||||||
|
return estimatedTotal;
|
||||||
|
|
||||||
|
estimatedTotal += (maxCombo - 200) * Math.Log(200) / Math.Log(4);
|
||||||
|
return estimatedTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
double estimateDroppedComboScoreAfterMiss(int lengthOfComboAfterMiss)
|
||||||
|
{
|
||||||
|
if (lengthOfComboAfterMiss >= 200)
|
||||||
|
lengthOfComboAfterMiss = 200;
|
||||||
|
|
||||||
|
// int_0^x (log_4(200) - log_4(t)) dt
|
||||||
|
// note that this is an pessimistic estimate, i.e. it may subtract too much if the miss happened before reaching 200 combo
|
||||||
|
return lengthOfComboAfterMiss * (1 + Math.Log(200) - Math.Log(lengthOfComboAfterMiss)) / Math.Log(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static double ComputeAccuracy(ScoreInfo scoreInfo)
|
public static double ComputeAccuracy(ScoreInfo scoreInfo)
|
||||||
{
|
{
|
||||||
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
|
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
|
||||||
|
@ -59,7 +59,8 @@ namespace osu.Game.Extensions
|
|||||||
/// <returns>A short relative string representing the input time.</returns>
|
/// <returns>A short relative string representing the input time.</returns>
|
||||||
public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff)
|
public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff)
|
||||||
{
|
{
|
||||||
if (time == default)
|
// covers all `DateTimeOffset` instances with the date portion of 0001-01-01.
|
||||||
|
if (time.Date == default)
|
||||||
return "-";
|
return "-";
|
||||||
|
|
||||||
var now = DateTime.Now;
|
var now = DateTime.Now;
|
||||||
|
@ -95,6 +95,7 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
case HitResult.SmallTickHit:
|
case HitResult.SmallTickHit:
|
||||||
case HitResult.LargeTickHit:
|
case HitResult.LargeTickHit:
|
||||||
|
case HitResult.SliderTailHit:
|
||||||
case HitResult.Great:
|
case HitResult.Great:
|
||||||
return Blue;
|
return Blue;
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
private Bindable<ConfineMouseMode> frameworkConfineMode;
|
private Bindable<ConfineMouseMode> frameworkConfineMode;
|
||||||
private Bindable<WindowMode> frameworkWindowMode;
|
private Bindable<WindowMode> frameworkWindowMode;
|
||||||
|
private Bindable<bool> frameworkMinimiseOnFocusLossInFullscreen;
|
||||||
|
|
||||||
private Bindable<OsuConfineMouseMode> osuConfineMode;
|
private Bindable<OsuConfineMouseMode> osuConfineMode;
|
||||||
private IBindable<bool> localUserPlaying;
|
private IBindable<bool> localUserPlaying;
|
||||||
@ -31,7 +32,9 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
|
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
|
||||||
frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
||||||
|
frameworkMinimiseOnFocusLossInFullscreen = frameworkConfigManager.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen);
|
||||||
frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
|
frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
|
||||||
|
frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode());
|
||||||
|
|
||||||
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
|
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
|
||||||
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
||||||
@ -46,7 +49,8 @@ namespace osu.Game.Input
|
|||||||
if (frameworkConfineMode.Disabled)
|
if (frameworkConfineMode.Disabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (frameworkWindowMode.Value == WindowMode.Fullscreen)
|
// override confine mode only when clicking outside the window minimises it.
|
||||||
|
if (frameworkWindowMode.Value == WindowMode.Fullscreen && frameworkMinimiseOnFocusLossInFullscreen.Value)
|
||||||
{
|
{
|
||||||
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
|
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
|
||||||
return;
|
return;
|
||||||
|
@ -152,9 +152,13 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "In order to change the renderer, the game will close. Please open it again."
|
/// "In order to change the renderer, the game will close. Please open it again."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ChangeRendererConfirmation =>
|
public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
||||||
new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
/// <summary>
|
||||||
|
/// "Minimise osu! when switching to another app"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Linq;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
@ -11,5 +12,8 @@ namespace osu.Game.Models
|
|||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string Hash { get; set; } = string.Empty;
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Backlink(nameof(RealmNamedFileUsage.File))]
|
||||||
|
public IQueryable<RealmNamedFileUsage> Usages { get; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ namespace osu.Game.Online.API
|
|||||||
public IBindable<APIUser> LocalUser => localUser;
|
public IBindable<APIUser> LocalUser => localUser;
|
||||||
public IBindableList<APIUser> Friends => friends;
|
public IBindableList<APIUser> Friends => friends;
|
||||||
public IBindable<UserActivity> Activity => activity;
|
public IBindable<UserActivity> Activity => activity;
|
||||||
|
public IBindable<UserStatistics> Statistics => statistics;
|
||||||
|
|
||||||
public Language Language => game.CurrentLanguage.Value;
|
public Language Language => game.CurrentLanguage.Value;
|
||||||
|
|
||||||
@ -62,6 +63,11 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
|
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
|
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
|
||||||
|
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
|
||||||
|
|
||||||
|
private Bindable<UserStatistics> statistics { get; } = new Bindable<UserStatistics>();
|
||||||
|
|
||||||
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
||||||
|
|
||||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||||
@ -85,12 +91,20 @@ namespace osu.Game.Online.API
|
|||||||
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
||||||
authentication.Token.ValueChanged += onTokenChanged;
|
authentication.Token.ValueChanged += onTokenChanged;
|
||||||
|
|
||||||
|
config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
|
||||||
|
|
||||||
localUser.BindValueChanged(u =>
|
localUser.BindValueChanged(u =>
|
||||||
{
|
{
|
||||||
u.OldValue?.Activity.UnbindFrom(activity);
|
u.OldValue?.Activity.UnbindFrom(activity);
|
||||||
u.NewValue.Activity.BindTo(activity);
|
u.NewValue.Activity.BindTo(activity);
|
||||||
|
|
||||||
|
if (u.OldValue != null)
|
||||||
|
localUserStatus.UnbindFrom(u.OldValue.Status);
|
||||||
|
localUserStatus.BindTo(u.NewValue.Status);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue);
|
||||||
|
|
||||||
var thread = new Thread(run)
|
var thread = new Thread(run)
|
||||||
{
|
{
|
||||||
Name = "APIAccess",
|
Name = "APIAccess",
|
||||||
@ -200,6 +214,7 @@ namespace osu.Game.Online.API
|
|||||||
setLocalUser(new APIUser
|
setLocalUser(new APIUser
|
||||||
{
|
{
|
||||||
Username = ProvidedUsername,
|
Username = ProvidedUsername,
|
||||||
|
Status = { Value = configStatus.Value ?? UserStatus.Online }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +261,7 @@ namespace osu.Game.Online.API
|
|||||||
};
|
};
|
||||||
userReq.Success += user =>
|
userReq.Success += user =>
|
||||||
{
|
{
|
||||||
// todo: save/pull from settings
|
user.Status.Value = configStatus.Value ?? UserStatus.Online;
|
||||||
user.Status.Value = UserStatus.Online;
|
|
||||||
|
|
||||||
setLocalUser(user);
|
setLocalUser(user);
|
||||||
|
|
||||||
@ -506,9 +520,21 @@ namespace osu.Game.Online.API
|
|||||||
flushQueue();
|
flushQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateStatistics(UserStatistics newStatistics)
|
||||||
|
{
|
||||||
|
statistics.Value = newStatistics;
|
||||||
|
|
||||||
|
if (IsLoggedIn)
|
||||||
|
localUser.Value.Statistics = newStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
private static APIUser createGuestUser() => new GuestUser();
|
private static APIUser createGuestUser() => new GuestUser();
|
||||||
|
|
||||||
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
localUser.Value = user;
|
||||||
|
statistics.Value = user.Statistics;
|
||||||
|
}, false);
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||||
|
|
||||||
|
public Bindable<UserStatistics?> Statistics { get; } = new Bindable<UserStatistics?>();
|
||||||
|
|
||||||
public Language Language => Language.en;
|
public Language Language => Language.en;
|
||||||
|
|
||||||
public string AccessToken => "token";
|
public string AccessToken => "token";
|
||||||
@ -115,6 +117,12 @@ namespace osu.Game.Online.API
|
|||||||
Id = DUMMY_USER_ID,
|
Id = DUMMY_USER_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Statistics.Value = new UserStatistics
|
||||||
|
{
|
||||||
|
GlobalRank = 1,
|
||||||
|
CountryRank = 1
|
||||||
|
};
|
||||||
|
|
||||||
state.Value = APIState.Online;
|
state.Value = APIState.Online;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +134,14 @@ namespace osu.Game.Online.API
|
|||||||
LocalUser.Value = new GuestUser();
|
LocalUser.Value = new GuestUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateStatistics(UserStatistics newStatistics)
|
||||||
|
{
|
||||||
|
Statistics.Value = newStatistics;
|
||||||
|
|
||||||
|
if (IsLoggedIn)
|
||||||
|
LocalUser.Value.Statistics = newStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
||||||
|
|
||||||
public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this);
|
public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this);
|
||||||
@ -141,6 +157,7 @@ namespace osu.Game.Online.API
|
|||||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||||
IBindableList<APIUser> IAPIProvider.Friends => Friends;
|
IBindableList<APIUser> IAPIProvider.Friends => Friends;
|
||||||
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
||||||
|
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// During the next simulated login, the process will fail immediately.
|
/// During the next simulated login, the process will fail immediately.
|
||||||
|
@ -28,6 +28,11 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<UserActivity> Activity { get; }
|
IBindable<UserActivity> Activity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current user's online statistics.
|
||||||
|
/// </summary>
|
||||||
|
IBindable<UserStatistics?> Statistics { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The language supplied by this provider to API requests.
|
/// The language supplied by this provider to API requests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -111,6 +116,11 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Logout();
|
void Logout();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets Statistics bindable.
|
||||||
|
/// </summary>
|
||||||
|
void UpdateStatistics(UserStatistics newStatistics);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="IHubClientConnector"/>. May be null if not supported.
|
/// Constructs a new <see cref="IHubClientConnector"/>. May be null if not supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -247,7 +247,7 @@ namespace osu.Game.Online.Chat
|
|||||||
string command = parameters[0];
|
string command = parameters[0];
|
||||||
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
|
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
|
||||||
|
|
||||||
switch (command)
|
switch (command.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case "np":
|
case "np":
|
||||||
AddInternal(new NowPlayingCommand(target));
|
AddInternal(new NowPlayingCommand(target));
|
||||||
|
@ -152,6 +152,15 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void RefetchScores() => Scheduler.AddOnce(refetchScores);
|
public void RefetchScores() => Scheduler.AddOnce(refetchScores);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all scores from the display.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearScores()
|
||||||
|
{
|
||||||
|
cancelPendingWork();
|
||||||
|
SetScores(null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call when a retrieval or display failure happened to show a relevant message to the user.
|
/// Call when a retrieval or display failure happened to show a relevant message to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -220,9 +229,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
{
|
{
|
||||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||||
|
|
||||||
cancelPendingWork();
|
ClearScores();
|
||||||
|
|
||||||
SetScores(null);
|
|
||||||
setState(LeaderboardState.Retrieving);
|
setState(LeaderboardState.Retrieving);
|
||||||
|
|
||||||
currentFetchCancellationSource = new CancellationTokenSource();
|
currentFetchCancellationSource = new CancellationTokenSource();
|
||||||
|
@ -23,7 +23,6 @@ namespace osu.Game.Online.Metadata
|
|||||||
public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence;
|
public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence;
|
||||||
private readonly BindableBool isWatchingUserPresence = new BindableBool();
|
private readonly BindableBool isWatchingUserPresence = new BindableBool();
|
||||||
|
|
||||||
// ReSharper disable once InconsistentlySynchronizedField
|
|
||||||
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
||||||
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
||||||
|
|
||||||
@ -192,7 +191,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
{
|
{
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (presence != null)
|
if (presence?.Status != null)
|
||||||
userStates[userId] = presence.Value;
|
userStates[userId] = presence.Value;
|
||||||
else
|
else
|
||||||
userStates.Remove(userId);
|
userStates.Remove(userId);
|
||||||
|
@ -127,6 +127,8 @@ namespace osu.Game.Online.Solo
|
|||||||
{
|
{
|
||||||
string rulesetName = callback.Score.Ruleset.ShortName;
|
string rulesetName = callback.Score.Ruleset.ShortName;
|
||||||
|
|
||||||
|
api.UpdateStatistics(updatedStatistics);
|
||||||
|
|
||||||
if (latestStatistics == null)
|
if (latestStatistics == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
|
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
|
||||||
|
|
||||||
private readonly Box flashLayer;
|
private readonly Box flashLayer;
|
||||||
private Sample flashSample = null!;
|
private Sample? flashSample;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
private readonly Container ring;
|
private readonly Container ring;
|
||||||
@ -267,7 +267,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
flashLayer.FadeInFromZero(80, Easing.OutQuint)
|
flashLayer.FadeInFromZero(80, Easing.OutQuint)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOutFromOne(1500, Easing.OutQuint);
|
.FadeOutFromOne(1500, Easing.OutQuint);
|
||||||
flashSample.Play();
|
flashSample?.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
@ -139,6 +139,8 @@ namespace osu.Game.Overlays.Login
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
panel.Status.BindValueChanged(_ => updateDropdownCurrent(), true);
|
||||||
|
|
||||||
dropdown.Current.BindValueChanged(action =>
|
dropdown.Current.BindValueChanged(action =>
|
||||||
{
|
{
|
||||||
switch (action.NewValue)
|
switch (action.NewValue)
|
||||||
@ -170,6 +172,24 @@ namespace osu.Game.Overlays.Login
|
|||||||
ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form));
|
ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void updateDropdownCurrent()
|
||||||
|
{
|
||||||
|
switch (panel.Status.Value)
|
||||||
|
{
|
||||||
|
case UserStatus.Online:
|
||||||
|
dropdown.Current.Value = UserAction.Online;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserStatus.DoNotDisturb:
|
||||||
|
dropdown.Current.Value = UserAction.DoNotDisturb;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserStatus.Offline:
|
||||||
|
dropdown.Current.Value = UserAction.AppearOffline;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e) => true;
|
protected override bool OnClick(ClickEvent e) => true;
|
||||||
|
@ -447,7 +447,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private void filterMods()
|
private void filterMods()
|
||||||
{
|
{
|
||||||
foreach (var modState in AllAvailableMods)
|
foreach (var modState in AllAvailableMods)
|
||||||
modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMultiplier()
|
private void updateMultiplier()
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
|
||||||
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,6 +14,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
protected override double RollingDuration => 500;
|
protected override double RollingDuration => 500;
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"0.00x");
|
protected override LocalisableString FormatCount(double count) => ModUtils.FormatScoreMultiplier(count);
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||||
{
|
{
|
||||||
|
@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private void updateEnabledState()
|
private void updateEnabledState()
|
||||||
{
|
{
|
||||||
Enabled.Value = availableMods.Value
|
Enabled.Value = availableMods.Value
|
||||||
.Where(pair => pair.Key != ModType.System)
|
|
||||||
.SelectMany(pair => pair.Value)
|
.SelectMany(pair => pair.Value)
|
||||||
|
.Where(modState => modState.ValidForSelection.Value)
|
||||||
.Any(modState => !modState.Active.Value && modState.Visible);
|
.Any(modState => !modState.Active.Value && modState.Visible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private SettingsDropdown<Size> resolutionDropdown = null!;
|
private SettingsDropdown<Size> resolutionDropdown = null!;
|
||||||
private SettingsDropdown<Display> displayDropdown = null!;
|
private SettingsDropdown<Display> displayDropdown = null!;
|
||||||
private SettingsDropdown<WindowMode> windowModeDropdown = null!;
|
private SettingsDropdown<WindowMode> windowModeDropdown = null!;
|
||||||
|
private SettingsCheckbox minimiseOnFocusLossCheckbox = null!;
|
||||||
private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
|
private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
|
||||||
|
|
||||||
private Bindable<float> scalingPositionX = null!;
|
private Bindable<float> scalingPositionX = null!;
|
||||||
@ -106,6 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
ItemSource = resolutions,
|
ItemSource = resolutions,
|
||||||
Current = sizeFullscreen
|
Current = sizeFullscreen
|
||||||
},
|
},
|
||||||
|
minimiseOnFocusLossCheckbox = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss,
|
||||||
|
Current = config.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen),
|
||||||
|
Keywords = new[] { "alt-tab", "minimize", "focus", "hide" },
|
||||||
|
},
|
||||||
safeAreaConsiderationsCheckbox = new SettingsCheckbox
|
safeAreaConsiderationsCheckbox = new SettingsCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Shrink game to avoid cameras and notches",
|
LabelText = "Shrink game to avoid cameras and notches",
|
||||||
@ -255,6 +262,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
{
|
{
|
||||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||||
|
minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
|
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
|
||||||
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
|
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
|
||||||
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
|
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
|
||||||
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey)));
|
// TODO: Distinct() can be removed after https://github.com/ppy/osu-framework/pull/6130 is merged.
|
||||||
|
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey).Distinct().ToArray()));
|
||||||
|
|
||||||
public void UpdateKeyCombination(KeyCombination newCombination)
|
public void UpdateKeyCombination(KeyCombination newCombination)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
private Bindable<double> localSensitivity;
|
private Bindable<double> localSensitivity;
|
||||||
|
|
||||||
private Bindable<WindowMode> windowMode;
|
private Bindable<WindowMode> windowMode;
|
||||||
|
private Bindable<bool> minimiseOnFocusLoss;
|
||||||
private SettingsEnumDropdown<OsuConfineMouseMode> confineMouseModeSetting;
|
private SettingsEnumDropdown<OsuConfineMouseMode> confineMouseModeSetting;
|
||||||
private Bindable<bool> relativeMode;
|
private Bindable<bool> relativeMode;
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
|
relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
|
||||||
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
||||||
|
minimiseOnFocusLoss = config.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -98,21 +100,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
|
localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
|
||||||
|
|
||||||
windowMode.BindValueChanged(mode =>
|
windowMode.BindValueChanged(_ => updateConfineMouseModeSettingVisibility());
|
||||||
{
|
minimiseOnFocusLoss.BindValueChanged(_ => updateConfineMouseModeSettingVisibility(), true);
|
||||||
bool isFullscreen = mode.NewValue == WindowMode.Fullscreen;
|
|
||||||
|
|
||||||
if (isFullscreen)
|
|
||||||
{
|
|
||||||
confineMouseModeSetting.Current.Disabled = true;
|
|
||||||
confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
confineMouseModeSetting.Current.Disabled = false;
|
|
||||||
confineMouseModeSetting.TooltipText = string.Empty;
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
highPrecisionMouse.Current.BindValueChanged(highPrecision =>
|
highPrecisionMouse.Current.BindValueChanged(highPrecision =>
|
||||||
{
|
{
|
||||||
@ -126,6 +115,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates disabled state and tooltip of <see cref="confineMouseModeSetting"/> to match when <see cref="ConfineMouseTracker"/> is overriding the confine mode.
|
||||||
|
/// </summary>
|
||||||
|
private void updateConfineMouseModeSettingVisibility()
|
||||||
|
{
|
||||||
|
bool confineModeOverriden = windowMode.Value == WindowMode.Fullscreen && minimiseOnFocusLoss.Value;
|
||||||
|
|
||||||
|
if (confineModeOverriden)
|
||||||
|
{
|
||||||
|
confineMouseModeSetting.Current.Disabled = true;
|
||||||
|
confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
confineMouseModeSetting.Current.Disabled = false;
|
||||||
|
confineMouseModeSetting.TooltipText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class SensitivitySetting : SettingsSlider<double, SensitivitySlider>
|
public partial class SensitivitySetting : SettingsSlider<double, SensitivitySlider>
|
||||||
{
|
{
|
||||||
public SensitivitySetting()
|
public SensitivitySetting()
|
||||||
|
@ -151,9 +151,12 @@ namespace osu.Game.Overlays
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (!headerTextVisibilityCache.IsValid)
|
if (!headerTextVisibilityCache.IsValid)
|
||||||
|
{
|
||||||
// These toolbox grouped may be contracted to only show icons.
|
// These toolbox grouped may be contracted to only show icons.
|
||||||
// For now, let's hide the header to avoid text truncation weirdness in such cases.
|
// For now, let's hide the header to avoid text truncation weirdness in such cases.
|
||||||
headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint);
|
headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint);
|
||||||
|
headerTextVisibilityCache.Validate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
public abstract partial class ToolbarButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
public abstract partial class ToolbarButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
public const float PADDING = 3;
|
||||||
|
|
||||||
protected GlobalAction? Hotkey { get; set; }
|
protected GlobalAction? Hotkey { get; set; }
|
||||||
|
|
||||||
public void SetIcon(Drawable icon)
|
public void SetIcon(Drawable icon)
|
||||||
@ -63,6 +65,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
|
|
||||||
protected virtual Anchor TooltipAnchor => Anchor.TopLeft;
|
protected virtual Anchor TooltipAnchor => Anchor.TopLeft;
|
||||||
|
|
||||||
|
protected readonly Container ButtonContent;
|
||||||
protected ConstrainedIconContainer IconContainer;
|
protected ConstrainedIconContainer IconContainer;
|
||||||
protected SpriteText DrawableText;
|
protected SpriteText DrawableText;
|
||||||
protected Box HoverBackground;
|
protected Box HoverBackground;
|
||||||
@ -80,59 +83,66 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
|
|
||||||
protected ToolbarButton()
|
protected ToolbarButton()
|
||||||
{
|
{
|
||||||
Width = Toolbar.HEIGHT;
|
AutoSizeAxes = Axes.X;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
Padding = new MarginPadding(3);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
BackgroundContent = new Container
|
ButtonContent = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Width = Toolbar.HEIGHT,
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 6,
|
|
||||||
CornerExponent = 3f,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
HoverBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.Gray(80).Opacity(180),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
flashBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
Colour = Color4.White.Opacity(100),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(5),
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 },
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
Padding = new MarginPadding(PADDING),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
IconContainer = new ConstrainedIconContainer
|
BackgroundContent = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.CentreLeft,
|
Masking = true,
|
||||||
Size = new Vector2(20),
|
CornerRadius = 6,
|
||||||
Alpha = 0,
|
CornerExponent = 3f,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
HoverBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(80).Opacity(180),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
flashBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Colour = Color4.White.Opacity(100),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
DrawableText = new OsuSpriteText
|
Flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Direction = FillDirection.Horizontal,
|
||||||
Origin = Anchor.CentreLeft,
|
Spacing = new Vector2(5),
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 },
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
IconContainer = new ConstrainedIconContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
DrawableText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -42,52 +42,59 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
clockDisplayMode = config.GetBindable<ToolbarClockDisplayMode>(OsuSetting.ToolbarClockDisplayMode);
|
clockDisplayMode = config.GetBindable<ToolbarClockDisplayMode>(OsuSetting.ToolbarClockDisplayMode);
|
||||||
prefer24HourTime = config.GetBindable<bool>(OsuSetting.Prefer24HourTime);
|
prefer24HourTime = config.GetBindable<bool>(OsuSetting.Prefer24HourTime);
|
||||||
|
|
||||||
Padding = new MarginPadding(3);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 6,
|
|
||||||
CornerExponent = 3f,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
hoverBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.Gray(80).Opacity(180),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
flashBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
Colour = Color4.White.Opacity(100),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Direction = FillDirection.Horizontal,
|
Padding = new MarginPadding(ToolbarButton.PADDING),
|
||||||
Spacing = new Vector2(5),
|
|
||||||
Padding = new MarginPadding(10),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
analog = new AnalogClockDisplay
|
new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.CentreLeft,
|
Masking = true,
|
||||||
|
CornerRadius = 6,
|
||||||
|
CornerExponent = 3f,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hoverBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(80).Opacity(180),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
flashBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Colour = Color4.White.Opacity(100),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
digital = new DigitalClockDisplay
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Origin = Anchor.CentreLeft,
|
AutoSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
analog = new AnalogClockDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
digital = new DigitalClockDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
public ToolbarHomeButton()
|
public ToolbarHomeButton()
|
||||||
{
|
{
|
||||||
Width *= 1.4f;
|
ButtonContent.Width *= 1.4f;
|
||||||
Hotkey = GlobalAction.Home;
|
Hotkey = GlobalAction.Home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
public ToolbarMusicButton()
|
public ToolbarMusicButton()
|
||||||
{
|
{
|
||||||
Hotkey = GlobalAction.ToggleNowPlaying;
|
Hotkey = GlobalAction.ToggleNowPlaying;
|
||||||
AutoSizeAxes = Axes.X;
|
ButtonContent.AutoSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
|
|
||||||
public RulesetButton()
|
public RulesetButton()
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(3)
|
ButtonContent.Padding = new MarginPadding(PADDING)
|
||||||
{
|
{
|
||||||
Bottom = 5
|
Bottom = 5
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
public ToolbarSettingsButton()
|
public ToolbarSettingsButton()
|
||||||
{
|
{
|
||||||
Width *= 1.4f;
|
ButtonContent.Width *= 1.4f;
|
||||||
Hotkey = GlobalAction.ToggleSettings;
|
Hotkey = GlobalAction.ToggleSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
|
|
||||||
public ToolbarUserButton()
|
public ToolbarUserButton()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.X;
|
ButtonContent.AutoSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -17,8 +17,6 @@ namespace osu.Game.Overlays.Wiki
|
|||||||
{
|
{
|
||||||
public partial class WikiHeader : BreadcrumbControlOverlayHeader
|
public partial class WikiHeader : BreadcrumbControlOverlayHeader
|
||||||
{
|
{
|
||||||
private const string index_path = "Main_Page";
|
|
||||||
|
|
||||||
public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex;
|
public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex;
|
||||||
|
|
||||||
public readonly Bindable<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>();
|
public readonly Bindable<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>();
|
||||||
@ -45,7 +43,7 @@ namespace osu.Game.Overlays.Wiki
|
|||||||
|
|
||||||
TabControl.AddItem(IndexPageString);
|
TabControl.AddItem(IndexPageString);
|
||||||
|
|
||||||
if (e.NewValue.Path == index_path)
|
if (e.NewValue.Path == WikiOverlay.INDEX_PATH)
|
||||||
{
|
{
|
||||||
Current.Value = IndexPageString;
|
Current.Value = IndexPageString;
|
||||||
return;
|
return;
|
||||||
|
@ -19,11 +19,11 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public partial class WikiOverlay : OnlineOverlay<WikiHeader>
|
public partial class WikiOverlay : OnlineOverlay<WikiHeader>
|
||||||
{
|
{
|
||||||
private const string index_path = @"main_page";
|
public const string INDEX_PATH = @"Main_page";
|
||||||
|
|
||||||
public string CurrentPath => path.Value;
|
public string CurrentPath => path.Value;
|
||||||
|
|
||||||
private readonly Bindable<string> path = new Bindable<string>(index_path);
|
private readonly Bindable<string> path = new Bindable<string>(INDEX_PATH);
|
||||||
|
|
||||||
private readonly Bindable<APIWikiPage> wikiData = new Bindable<APIWikiPage>();
|
private readonly Bindable<APIWikiPage> wikiData = new Bindable<APIWikiPage>();
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowPage(string pagePath = index_path)
|
public void ShowPage(string pagePath = INDEX_PATH)
|
||||||
{
|
{
|
||||||
path.Value = pagePath.Trim('/');
|
path.Value = pagePath.Trim('/');
|
||||||
Show();
|
Show();
|
||||||
@ -137,7 +137,7 @@ namespace osu.Game.Overlays
|
|||||||
wikiData.Value = response;
|
wikiData.Value = response;
|
||||||
path.Value = response.Path;
|
path.Value = response.Path;
|
||||||
|
|
||||||
if (response.Layout == index_path)
|
if (response.Layout.Equals(INDEX_PATH, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
LoadDisplay(new WikiMainPage
|
LoadDisplay(new WikiMainPage
|
||||||
{
|
{
|
||||||
@ -161,7 +161,7 @@ namespace osu.Game.Overlays
|
|||||||
path.Value = "error";
|
path.Value = "error";
|
||||||
|
|
||||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
||||||
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page)."));
|
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page]({INDEX_PATH})."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showParentPage()
|
private void showParentPage()
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModCinema;
|
public override IconUsage? Icon => OsuIcon.ModCinema;
|
||||||
public override LocalisableString Description => "Watch the video without visual distractions.";
|
public override LocalisableString Description => "Watch the video without visual distractions.";
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }).ToArray();
|
||||||
|
|
||||||
public void ApplyToHUD(HUDOverlay overlay)
|
public void ApplyToHUD(HUDOverlay overlay)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
|
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModCinema) };
|
||||||
|
|
||||||
[SettingSource("Restart on fail", "Automatically restarts when failed.")]
|
[SettingSource("Restart on fail", "Automatically restarts when failed.")]
|
||||||
public BindableBool Restart { get; } = new BindableBool();
|
public BindableBool Restart { get; } = new BindableBool();
|
||||||
|
@ -56,9 +56,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
Combo.BindTo(scoreProcessor.Combo);
|
Combo.BindTo(scoreProcessor.Combo);
|
||||||
|
|
||||||
// Default value of ScoreProcessor's Rank in Flashlight Mod should be SS+
|
|
||||||
scoreProcessor.Rank.Value = ScoreRank.XH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy)
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy)
|
||||||
|
@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
// Default value of ScoreProcessor's Rank in Hidden Mod should be SS+
|
|
||||||
scoreProcessor.Rank.Value = ScoreRank.XH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy)
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy)
|
||||||
|
@ -599,7 +599,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2;
|
float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2;
|
||||||
double returnedValue = balanceAdjustAmount * (position - 0.5f);
|
double returnedValue = balanceAdjustAmount * (position - 0.5f);
|
||||||
|
|
||||||
return returnedValue;
|
// Rounded to reduce the overhead of audio adjustments (which are currently bindable heavy).
|
||||||
|
// Balance is very hard to perceive in small increments anyways.
|
||||||
|
return Math.Round(returnedValue, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -57,5 +57,40 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1);
|
return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* WARNING: DO NOT TOUCH IF YOU DO NOT KNOW WHAT YOU ARE DOING
|
||||||
|
*
|
||||||
|
* It so happens that in stable, due to .NET Framework internals, float math would be performed
|
||||||
|
* using x87 registers and opcodes.
|
||||||
|
* .NET (Core) however uses SSE instructions on 32- and 64-bit words.
|
||||||
|
* x87 registers are _80 bits_ wide. Which is notably wider than _both_ float and double.
|
||||||
|
* Therefore, on a significant number of beatmaps, the rounding would not produce correct values.
|
||||||
|
*
|
||||||
|
* Thus, to crudely - but, seemingly *mostly* accurately, after checking across all ranked maps - emulate this,
|
||||||
|
* use `decimal`, which is slow, but has bigger precision than `double`.
|
||||||
|
* At the time of writing, there is _one_ ranked exception to this - namely https://osu.ppy.sh/beatmapsets/1156087#osu/2625853 -
|
||||||
|
* but it is considered an "acceptable casualty", since in that case scores aren't inflated by _that_ much compared to others.
|
||||||
|
*/
|
||||||
|
|
||||||
|
decimal objectToDrainRatio = drainLength != 0
|
||||||
|
? Math.Clamp((decimal)objectCount / drainLength * 8, 0, 16)
|
||||||
|
: 16;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Notably, THE `double` CASTS BELOW ARE IMPORTANT AND MUST REMAIN.
|
||||||
|
* Their goal is to trick the compiler / runtime into NOT promoting from single-precision float, as doing so would prompt it
|
||||||
|
* to attempt to "silently" fix the single-precision values when converting to decimal,
|
||||||
|
* which is NOT what the x87 FPU does.
|
||||||
|
*/
|
||||||
|
|
||||||
|
decimal drainRate = (decimal)(double)difficulty.DrainRate;
|
||||||
|
decimal overallDifficulty = (decimal)(double)difficulty.OverallDifficulty;
|
||||||
|
decimal circleSize = (decimal)(double)difficulty.CircleSize;
|
||||||
|
|
||||||
|
return (int)Math.Round((drainRate + overallDifficulty + circleSize + objectToDrainRatio) / 38 * 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Description(@"")]
|
[Description(@"")]
|
||||||
[EnumMember(Value = "none")]
|
[EnumMember(Value = "none")]
|
||||||
[Order(14)]
|
[Order(15)]
|
||||||
None,
|
None,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// Indicates small tick miss.
|
/// Indicates small tick miss.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EnumMember(Value = "small_tick_miss")]
|
[EnumMember(Value = "small_tick_miss")]
|
||||||
[Order(11)]
|
[Order(12)]
|
||||||
SmallTickMiss,
|
SmallTickMiss,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[EnumMember(Value = "large_tick_miss")]
|
[EnumMember(Value = "large_tick_miss")]
|
||||||
[Description("-")]
|
[Description("-")]
|
||||||
[Order(10)]
|
[Order(11)]
|
||||||
LargeTickMiss,
|
LargeTickMiss,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("S Bonus")]
|
[Description("S Bonus")]
|
||||||
[EnumMember(Value = "small_bonus")]
|
[EnumMember(Value = "small_bonus")]
|
||||||
[Order(9)]
|
[Order(10)]
|
||||||
SmallBonus,
|
SmallBonus,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("L Bonus")]
|
[Description("L Bonus")]
|
||||||
[EnumMember(Value = "large_bonus")]
|
[EnumMember(Value = "large_bonus")]
|
||||||
[Order(8)]
|
[Order(9)]
|
||||||
LargeBonus,
|
LargeBonus,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -119,14 +119,14 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[EnumMember(Value = "ignore_miss")]
|
[EnumMember(Value = "ignore_miss")]
|
||||||
[Description("-")]
|
[Description("-")]
|
||||||
[Order(13)]
|
[Order(14)]
|
||||||
IgnoreMiss,
|
IgnoreMiss,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates a hit that should be ignored for scoring purposes.
|
/// Indicates a hit that should be ignored for scoring purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EnumMember(Value = "ignore_hit")]
|
[EnumMember(Value = "ignore_hit")]
|
||||||
[Order(12)]
|
[Order(13)]
|
||||||
IgnoreHit,
|
IgnoreHit,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -136,14 +136,14 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// May be paired with <see cref="IgnoreHit"/>.
|
/// May be paired with <see cref="IgnoreHit"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[EnumMember(Value = "combo_break")]
|
[EnumMember(Value = "combo_break")]
|
||||||
[Order(15)]
|
[Order(16)]
|
||||||
ComboBreak,
|
ComboBreak,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A special judgement similar to <see cref="LargeTickHit"/> that's used to increase the valuation of the final tick of a slider.
|
/// A special judgement similar to <see cref="LargeTickHit"/> that's used to increase the valuation of the final tick of a slider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EnumMember(Value = "slider_tail_hit")]
|
[EnumMember(Value = "slider_tail_hit")]
|
||||||
[Order(16)]
|
[Order(8)]
|
||||||
SliderTailHit,
|
SliderTailHit,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -86,7 +86,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current rank.
|
/// The current rank.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Bindable<ScoreRank> Rank = new Bindable<ScoreRank>(ScoreRank.X);
|
public IBindable<ScoreRank> Rank => rank;
|
||||||
|
|
||||||
|
private readonly Bindable<ScoreRank> rank = new Bindable<ScoreRank>(ScoreRank.X);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The highest combo achieved by this score.
|
/// The highest combo achieved by this score.
|
||||||
@ -167,14 +169,14 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (!beatmapApplied)
|
if (!beatmapApplied)
|
||||||
throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}.");
|
throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}.");
|
||||||
|
|
||||||
return new Dictionary<HitResult, int>(maximumResultCounts);
|
return new Dictionary<HitResult, int>(MaximumResultCounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool beatmapApplied;
|
private bool beatmapApplied;
|
||||||
|
|
||||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
protected readonly Dictionary<HitResult, int> ScoreResultCounts = new Dictionary<HitResult, int>();
|
||||||
private readonly Dictionary<HitResult, int> maximumResultCounts = new Dictionary<HitResult, int>();
|
protected readonly Dictionary<HitResult, int> MaximumResultCounts = new Dictionary<HitResult, int>();
|
||||||
|
|
||||||
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
||||||
private HitObject? lastHitObject;
|
private HitObject? lastHitObject;
|
||||||
@ -186,9 +188,13 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||||
Accuracy.ValueChanged += accuracy =>
|
Accuracy.ValueChanged += accuracy =>
|
||||||
{
|
{
|
||||||
Rank.Value = RankFromAccuracy(accuracy.NewValue);
|
// Once failed, we shouldn't update the rank anymore.
|
||||||
|
if (rank.Value == ScoreRank.F)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rank.Value = RankFromAccuracy(accuracy.NewValue);
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
Mods.ValueChanged += mods =>
|
Mods.ValueChanged += mods =>
|
||||||
@ -216,7 +222,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (result.FailedAtJudgement)
|
if (result.FailedAtJudgement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) + 1;
|
||||||
|
|
||||||
if (result.Type.IncreasesCombo())
|
if (result.Type.IncreasesCombo())
|
||||||
Combo.Value++;
|
Combo.Value++;
|
||||||
@ -272,7 +278,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (result.FailedAtJudgement)
|
if (result.FailedAtJudgement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) - 1;
|
||||||
|
|
||||||
if (result.Judgement.MaxResult.AffectsAccuracy())
|
if (result.Judgement.MaxResult.AffectsAccuracy())
|
||||||
{
|
{
|
||||||
@ -394,13 +400,13 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
maximumComboPortion = currentComboPortion;
|
maximumComboPortion = currentComboPortion;
|
||||||
maximumAccuracyJudgementCount = currentAccuracyJudgementCount;
|
maximumAccuracyJudgementCount = currentAccuracyJudgementCount;
|
||||||
|
|
||||||
maximumResultCounts.Clear();
|
MaximumResultCounts.Clear();
|
||||||
maximumResultCounts.AddRange(scoreResultCounts);
|
MaximumResultCounts.AddRange(ScoreResultCounts);
|
||||||
|
|
||||||
MaximumTotalScore = TotalScore.Value;
|
MaximumTotalScore = TotalScore.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
scoreResultCounts.Clear();
|
ScoreResultCounts.Clear();
|
||||||
|
|
||||||
currentBaseScore = 0;
|
currentBaseScore = 0;
|
||||||
currentMaximumBaseScore = 0;
|
currentMaximumBaseScore = 0;
|
||||||
@ -411,8 +417,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
TotalScore.Value = 0;
|
TotalScore.Value = 0;
|
||||||
Accuracy.Value = 1;
|
Accuracy.Value = 1;
|
||||||
Combo.Value = 0;
|
Combo.Value = 0;
|
||||||
Rank.Disabled = false;
|
rank.Value = ScoreRank.X;
|
||||||
Rank.Value = ScoreRank.X;
|
|
||||||
HighestCombo.Value = 0;
|
HighestCombo.Value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,10 +435,10 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
score.MaximumStatistics.Clear();
|
score.MaximumStatistics.Clear();
|
||||||
|
|
||||||
foreach (var result in HitResultExtensions.ALL_TYPES)
|
foreach (var result in HitResultExtensions.ALL_TYPES)
|
||||||
score.Statistics[result] = scoreResultCounts.GetValueOrDefault(result);
|
score.Statistics[result] = ScoreResultCounts.GetValueOrDefault(result);
|
||||||
|
|
||||||
foreach (var result in HitResultExtensions.ALL_TYPES)
|
foreach (var result in HitResultExtensions.ALL_TYPES)
|
||||||
score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result);
|
score.MaximumStatistics[result] = MaximumResultCounts.GetValueOrDefault(result);
|
||||||
|
|
||||||
// Populate total score after everything else.
|
// Populate total score after everything else.
|
||||||
score.TotalScore = TotalScore.Value;
|
score.TotalScore = TotalScore.Value;
|
||||||
@ -448,7 +453,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
score.Passed = false;
|
score.Passed = false;
|
||||||
Rank.Value = ScoreRank.F;
|
rank.Value = ScoreRank.F;
|
||||||
|
|
||||||
PopulateScore(score);
|
PopulateScore(score);
|
||||||
}
|
}
|
||||||
@ -464,8 +469,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
HighestCombo.Value = frame.Header.MaxCombo;
|
HighestCombo.Value = frame.Header.MaxCombo;
|
||||||
TotalScore.Value = frame.Header.TotalScore;
|
TotalScore.Value = frame.Header.TotalScore;
|
||||||
|
|
||||||
scoreResultCounts.Clear();
|
ScoreResultCounts.Clear();
|
||||||
scoreResultCounts.AddRange(frame.Header.Statistics);
|
ScoreResultCounts.AddRange(frame.Header.Statistics);
|
||||||
|
|
||||||
SetScoreProcessorStatistics(frame.Header.ScoreProcessorStatistics);
|
SetScoreProcessorStatistics(frame.Header.ScoreProcessorStatistics);
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.IO.Legacy;
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
@ -36,9 +37,15 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
|
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
|
||||||
/// <item><description>30000008: Add accuracy conversion. Reconvert all scores.</description></item>
|
/// <item><description>30000008: Add accuracy conversion. Reconvert all scores.</description></item>
|
||||||
/// <item><description>30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.</description></item>
|
/// <item><description>30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.</description></item>
|
||||||
|
/// <item><description>30000010: Fix mania score V1 conversion using score V1 accuracy rather than V2 accuracy. Reconvert all scores.</description></item>
|
||||||
|
/// <item><description>30000011: Re-do catch scoring to mirror stable Score V2 as closely as feasible. Reconvert all scores.</description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct
|
||||||
|
/// <see cref="LegacyRulesetExtensions.CalculateDifficultyPeppyStars"/> method. Reconvert all scores.
|
||||||
|
/// </description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public const int LATEST_VERSION = 30000009;
|
public const int LATEST_VERSION = 30000012;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
||||||
|
@ -62,6 +62,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Button != MouseButton.Left)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (rotationHandler == null) return false;
|
if (rotationHandler == null) return false;
|
||||||
|
|
||||||
rotationHandler.Begin();
|
rotationHandler.Begin();
|
||||||
|
@ -95,6 +95,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
Colour = Color4.Black
|
Colour = Color4.Black
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public override bool? AllowGlobalTrackControl => false;
|
||||||
|
|
||||||
protected IntroScreen([CanBeNull] Func<MainMenu> createNextScreen = null)
|
protected IntroScreen([CanBeNull] Func<MainMenu> createNextScreen = null)
|
||||||
{
|
{
|
||||||
this.createNextScreen = createNextScreen;
|
this.createNextScreen = createNextScreen;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -25,6 +26,7 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
using osu.Game.Overlays.SkinEditor;
|
using osu.Game.Overlays.SkinEditor;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
@ -49,6 +51,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public override bool AllowExternalScreenChange => true;
|
public override bool AllowExternalScreenChange => true;
|
||||||
|
|
||||||
|
public override bool? AllowGlobalTrackControl => true;
|
||||||
|
|
||||||
private Screen songSelect;
|
private Screen songSelect;
|
||||||
|
|
||||||
private MenuSideFlashes sideFlashes;
|
private MenuSideFlashes sideFlashes;
|
||||||
@ -390,7 +394,12 @@ namespace osu.Game.Screens.Menu
|
|||||||
if (requiresConfirmation)
|
if (requiresConfirmation)
|
||||||
{
|
{
|
||||||
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
||||||
exitDialog.PerformOkAction();
|
{
|
||||||
|
if (exitDialog.Buttons.OfType<PopupDialogOkButton>().FirstOrDefault() != null)
|
||||||
|
exitDialog.PerformOkAction();
|
||||||
|
else
|
||||||
|
exitDialog.Flash();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dialogOverlay.Push(new ConfirmExitDialog(() =>
|
dialogOverlay.Push(new ConfirmExitDialog(() =>
|
||||||
|
@ -349,6 +349,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
addItemButton.Alpha = localUserCanAddItem ? 1 : 0;
|
addItemButton.Alpha = localUserCanAddItem ? 1 : 0;
|
||||||
|
|
||||||
Scheduler.AddOnce(UpdateMods);
|
Scheduler.AddOnce(UpdateMods);
|
||||||
|
|
||||||
|
Activity.Value = new UserActivity.InLobby(Room);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly;
|
private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly;
|
||||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
// Disallow fails in multiplayer for now.
|
|
||||||
protected override bool CheckModsAllowFailure() => false;
|
|
||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -55,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
AllowPause = false,
|
AllowPause = false,
|
||||||
AllowRestart = false,
|
AllowRestart = false,
|
||||||
|
AllowFailAnimation = false,
|
||||||
AllowSkipping = room.AutoSkip.Value,
|
AllowSkipping = room.AutoSkip.Value,
|
||||||
AutomaticallySkipIntro = room.AutoSkip.Value,
|
AutomaticallySkipIntro = room.AutoSkip.Value,
|
||||||
AlwaysShowLeaderboard = true,
|
AlwaysShowLeaderboard = true,
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
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;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.Break;
|
using osu.Game.Screens.Play.Break;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
@ -113,7 +115,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (scoreProcessor != null)
|
if (scoreProcessor != null)
|
||||||
{
|
{
|
||||||
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
|
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
|
||||||
info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
|
((IBindable<ScoreRank>)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play
|
|||||||
public bool HasPassed { get; set; }
|
public bool HasPassed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the user failed during gameplay.
|
/// Whether the user failed during gameplay. This is only set when the gameplay session has completed due to the fail.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasFailed { get; set; }
|
public bool HasFailed { get; set; }
|
||||||
|
|
||||||
|
@ -83,12 +83,14 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
},
|
},
|
||||||
fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft)
|
fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft)
|
||||||
{
|
{
|
||||||
|
RequiredDisplayDigits = { Value = 2 },
|
||||||
WireframeOpacity = { BindTarget = WireframeOpacity },
|
WireframeOpacity = { BindTarget = WireframeOpacity },
|
||||||
Scale = new Vector2(0.5f),
|
Scale = new Vector2(0.5f),
|
||||||
},
|
},
|
||||||
percentText = new ArgonCounterTextComponent(Anchor.TopLeft)
|
percentText = new ArgonCounterTextComponent(Anchor.TopLeft)
|
||||||
{
|
{
|
||||||
Text = @"%",
|
Text = @"%",
|
||||||
|
RequiredDisplayDigits = { Value = 1 },
|
||||||
WireframeOpacity = { BindTarget = WireframeOpacity }
|
WireframeOpacity = { BindTarget = WireframeOpacity }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,31 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int DisplayedCount
|
||||||
|
{
|
||||||
|
get => base.DisplayedCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.DisplayedCount = value;
|
||||||
|
updateWireframe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWireframe()
|
||||||
|
{
|
||||||
|
text.RequiredDisplayDigits.Value = getDigitsRequiredForDisplayCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDigitsRequiredForDisplayCount()
|
||||||
|
{
|
||||||
|
// one for the single presumed starting digit, one for the "x" at the end.
|
||||||
|
int digitsRequired = 2;
|
||||||
|
long c = DisplayedCount;
|
||||||
|
while ((c /= 10) > 0)
|
||||||
|
digitsRequired++;
|
||||||
|
return digitsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(int count) => $@"{count}x";
|
protected override LocalisableString FormatCount(int count) => $@"{count}x";
|
||||||
|
|
||||||
protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper())
|
protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper())
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// 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;
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -33,14 +33,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public LocalisableString Text
|
public LocalisableString Text
|
||||||
{
|
{
|
||||||
get => textPart.Text;
|
get => textPart.Text;
|
||||||
set
|
set => textPart.Text = value;
|
||||||
{
|
|
||||||
int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit);
|
|
||||||
string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty;
|
|
||||||
|
|
||||||
wireframesPart.Text = remainingText + value;
|
|
||||||
textPart.Text = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null)
|
public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null)
|
||||||
@ -81,6 +74,8 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RequiredDisplayDigits.BindValueChanged(digits => wireframesPart.Text = new string('#', digits.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string textLookup(char c)
|
private string textLookup(char c)
|
||||||
@ -137,33 +132,49 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures)
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
|
const string font_name = @"argon-counter";
|
||||||
|
|
||||||
Spacing = new Vector2(-2f, 0f);
|
Spacing = new Vector2(-2f, 0f);
|
||||||
Font = new FontUsage(@"argon-counter", 1);
|
Font = new FontUsage(font_name, 1);
|
||||||
glyphStore = new GlyphStore(textures, getLookup);
|
glyphStore = new GlyphStore(font_name, textures, getLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
|
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
|
||||||
|
|
||||||
private class GlyphStore : ITexturedGlyphLookupStore
|
private class GlyphStore : ITexturedGlyphLookupStore
|
||||||
{
|
{
|
||||||
|
private readonly string fontName;
|
||||||
private readonly TextureStore textures;
|
private readonly TextureStore textures;
|
||||||
private readonly Func<char, string> getLookup;
|
private readonly Func<char, string> getLookup;
|
||||||
|
|
||||||
public GlyphStore(TextureStore textures, Func<char, string> getLookup)
|
private readonly Dictionary<char, ITexturedCharacterGlyph?> cache = new Dictionary<char, ITexturedCharacterGlyph?>();
|
||||||
|
|
||||||
|
public GlyphStore(string fontName, TextureStore textures, Func<char, string> getLookup)
|
||||||
{
|
{
|
||||||
|
this.fontName = fontName;
|
||||||
this.textures = textures;
|
this.textures = textures;
|
||||||
this.getLookup = getLookup;
|
this.getLookup = getLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITexturedCharacterGlyph? Get(string? fontName, char character)
|
public ITexturedCharacterGlyph? Get(string? fontName, char character)
|
||||||
{
|
{
|
||||||
|
// We only service one font.
|
||||||
|
if (fontName != this.fontName)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (cache.TryGetValue(character, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
string lookup = getLookup(character);
|
string lookup = getLookup(character);
|
||||||
var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}");
|
var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}");
|
||||||
|
|
||||||
if (texture == null)
|
TexturedCharacterGlyph? glyph = null;
|
||||||
return null;
|
|
||||||
|
|
||||||
return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f);
|
if (texture != null)
|
||||||
|
glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f);
|
||||||
|
|
||||||
|
cache[character] = glyph;
|
||||||
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
|
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
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.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -58,39 +58,12 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private bool displayingMiss => resetMissBarDelegate != null;
|
private bool displayingMiss => resetMissBarDelegate != null;
|
||||||
|
|
||||||
private readonly List<Vector2> missBarVertices = new List<Vector2>();
|
private readonly List<Vector2> vertices = new List<Vector2>();
|
||||||
private readonly List<Vector2> healthBarVertices = new List<Vector2>();
|
|
||||||
|
|
||||||
private double glowBarValue;
|
private double glowBarValue;
|
||||||
|
|
||||||
public double GlowBarValue
|
|
||||||
{
|
|
||||||
get => glowBarValue;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (glowBarValue == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
glowBarValue = value;
|
|
||||||
Scheduler.AddOnce(updatePathVertices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private double healthBarValue;
|
private double healthBarValue;
|
||||||
|
|
||||||
public double HealthBarValue
|
|
||||||
{
|
|
||||||
get => healthBarValue;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (healthBarValue == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
healthBarValue = value;
|
|
||||||
Scheduler.AddOnce(updatePathVertices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public const float MAIN_PATH_RADIUS = 10f;
|
public const float MAIN_PATH_RADIUS = 10f;
|
||||||
|
|
||||||
private const float curve_start_offset = 70;
|
private const float curve_start_offset = 70;
|
||||||
@ -100,6 +73,8 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
|
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
|
||||||
|
|
||||||
|
private readonly Cached pathVerticesCache = new Cached();
|
||||||
|
|
||||||
public ArgonHealthDisplay()
|
public ArgonHealthDisplay()
|
||||||
{
|
{
|
||||||
AddLayout(drawSizeLayout);
|
AddLayout(drawSizeLayout);
|
||||||
@ -158,7 +133,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
HealthProcessor.NewJudgement += onNewJudgement;
|
HealthProcessor.NewJudgement += onNewJudgement;
|
||||||
Current.BindValueChanged(onCurrentChanged, true);
|
|
||||||
|
|
||||||
// we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
|
// we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
|
||||||
// setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
|
// setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
|
||||||
@ -173,31 +147,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit;
|
private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit;
|
||||||
|
|
||||||
private void onCurrentChanged(ValueChangedEvent<double> valueChangedEvent)
|
|
||||||
// schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one).
|
|
||||||
=> Scheduler.AddOnce(updateDisplay);
|
|
||||||
|
|
||||||
private void updateDisplay()
|
|
||||||
{
|
|
||||||
double newHealth = Current.Value;
|
|
||||||
|
|
||||||
if (newHealth >= GlowBarValue)
|
|
||||||
finishMissDisplay();
|
|
||||||
|
|
||||||
double time = newHealth > GlowBarValue ? 500 : 250;
|
|
||||||
|
|
||||||
// TODO: this should probably use interpolation in update.
|
|
||||||
this.TransformTo(nameof(HealthBarValue), newHealth, time, Easing.OutQuint);
|
|
||||||
|
|
||||||
if (pendingMissAnimation && newHealth < GlowBarValue)
|
|
||||||
triggerMissDisplay();
|
|
||||||
|
|
||||||
pendingMissAnimation = false;
|
|
||||||
|
|
||||||
if (!displayingMiss)
|
|
||||||
this.TransformTo(nameof(GlowBarValue), newHealth, time, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -208,30 +157,44 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
drawSizeLayout.Validate();
|
drawSizeLayout.Validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
healthBarValue = Interpolation.DampContinuously(healthBarValue, Current.Value, 50, Time.Elapsed);
|
||||||
|
if (!displayingMiss)
|
||||||
|
glowBarValue = Interpolation.DampContinuously(glowBarValue, Current.Value, 50, Time.Elapsed);
|
||||||
|
|
||||||
mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed);
|
mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed);
|
||||||
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
|
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
|
||||||
|
|
||||||
|
updatePathVertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HealthChanged(bool increase)
|
||||||
|
{
|
||||||
|
if (Current.Value >= glowBarValue)
|
||||||
|
finishMissDisplay();
|
||||||
|
|
||||||
|
if (pendingMissAnimation)
|
||||||
|
{
|
||||||
|
triggerMissDisplay();
|
||||||
|
pendingMissAnimation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.HealthChanged(increase);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FinishInitialAnimation(double value)
|
protected override void FinishInitialAnimation(double value)
|
||||||
{
|
{
|
||||||
base.FinishInitialAnimation(value);
|
base.FinishInitialAnimation(value);
|
||||||
this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint);
|
this.TransformTo(nameof(healthBarValue), value, 500, Easing.OutQuint);
|
||||||
this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint);
|
this.TransformTo(nameof(glowBarValue), value, 250, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Flash()
|
protected override void Flash()
|
||||||
{
|
{
|
||||||
base.Flash();
|
base.Flash();
|
||||||
|
|
||||||
mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f))
|
|
||||||
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
|
|
||||||
|
|
||||||
if (!displayingMiss)
|
if (!displayingMiss)
|
||||||
{
|
{
|
||||||
glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint)
|
// TODO: REMOVE THIS. It's recreating textures.
|
||||||
.Then()
|
|
||||||
.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 1000, Easing.OutQuint);
|
|
||||||
|
|
||||||
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint)
|
glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint)
|
||||||
.Then()
|
.Then()
|
||||||
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
|
.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint);
|
||||||
@ -245,13 +208,15 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
this.Delay(500).Schedule(() =>
|
this.Delay(500).Schedule(() =>
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(GlowBarValue), Current.Value, 300, Easing.OutQuint);
|
this.TransformTo(nameof(glowBarValue), Current.Value, 300, Easing.OutQuint);
|
||||||
finishMissDisplay();
|
finishMissDisplay();
|
||||||
}, out resetMissBarDelegate);
|
}, out resetMissBarDelegate);
|
||||||
|
|
||||||
|
// TODO: REMOVE THIS. It's recreating textures.
|
||||||
glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then()
|
glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then()
|
||||||
.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint);
|
.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint);
|
||||||
|
|
||||||
|
// TODO: REMOVE THIS. It's recreating textures.
|
||||||
glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f))
|
glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f))
|
||||||
.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint);
|
.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
@ -263,6 +228,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
if (Current.Value > 0)
|
if (Current.Value > 0)
|
||||||
{
|
{
|
||||||
|
// TODO: REMOVE THIS. It's recreating textures.
|
||||||
glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In);
|
glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In);
|
||||||
glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In);
|
glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In);
|
||||||
}
|
}
|
||||||
@ -302,7 +268,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (DrawWidth - padding < rescale_cutoff)
|
if (DrawWidth - padding < rescale_cutoff)
|
||||||
rescalePathProportionally();
|
rescalePathProportionally();
|
||||||
|
|
||||||
List<Vector2> vertices = new List<Vector2>();
|
|
||||||
barPath.GetPathToProgress(vertices, 0.0, 1.0);
|
barPath.GetPathToProgress(vertices, 0.0, 1.0);
|
||||||
|
|
||||||
background.Vertices = vertices;
|
background.Vertices = vertices;
|
||||||
@ -332,20 +297,25 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private void updatePathVertices()
|
private void updatePathVertices()
|
||||||
{
|
{
|
||||||
barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue);
|
barPath.GetPathToProgress(vertices, 0.0, healthBarValue);
|
||||||
barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(glowBarValue, healthBarValue));
|
if (vertices.Count == 0) vertices.Add(Vector2.Zero);
|
||||||
|
Vector2 initialVertex = vertices[0];
|
||||||
|
for (int i = 0; i < vertices.Count; i++)
|
||||||
|
vertices[i] -= initialVertex;
|
||||||
|
|
||||||
if (healthBarVertices.Count == 0)
|
mainBar.Vertices = vertices;
|
||||||
healthBarVertices.Add(Vector2.Zero);
|
mainBar.Position = initialVertex;
|
||||||
|
|
||||||
if (missBarVertices.Count == 0)
|
barPath.GetPathToProgress(vertices, healthBarValue, Math.Max(glowBarValue, healthBarValue));
|
||||||
missBarVertices.Add(Vector2.Zero);
|
if (vertices.Count == 0) vertices.Add(Vector2.Zero);
|
||||||
|
initialVertex = vertices[0];
|
||||||
|
for (int i = 0; i < vertices.Count; i++)
|
||||||
|
vertices[i] -= initialVertex;
|
||||||
|
|
||||||
glowBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList();
|
glowBar.Vertices = vertices;
|
||||||
glowBar.Position = missBarVertices[0];
|
glowBar.Position = initialVertex;
|
||||||
|
|
||||||
mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList();
|
pathVerticesCache.Validate();
|
||||||
mainBar.Position = healthBarVertices[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -358,14 +328,17 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private partial class BackgroundPath : SmoothPath
|
private partial class BackgroundPath : SmoothPath
|
||||||
{
|
{
|
||||||
|
private static readonly Color4 colour_white = Color4.White.Opacity(0.8f);
|
||||||
|
private static readonly Color4 colour_black = Color4.Black.Opacity(0.2f);
|
||||||
|
|
||||||
protected override Color4 ColourAt(float position)
|
protected override Color4 ColourAt(float position)
|
||||||
{
|
{
|
||||||
if (position <= 0.16f)
|
if (position <= 0.16f)
|
||||||
return Color4.White.Opacity(0.8f);
|
return colour_white;
|
||||||
|
|
||||||
return Interpolation.ValueAt(position,
|
return Interpolation.ValueAt(position,
|
||||||
Color4.White.Opacity(0.8f),
|
colour_white,
|
||||||
Color4.Black.Opacity(0.2f),
|
colour_black,
|
||||||
-0.5f, 1f, Easing.OutQuint);
|
-0.5f, 1f, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,12 +377,14 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
public float GlowPortion { get; init; }
|
public float GlowPortion { get; init; }
|
||||||
|
|
||||||
|
private static readonly Colour4 transparent_black = Colour4.Black.Opacity(0.0f);
|
||||||
|
|
||||||
protected override Color4 ColourAt(float position)
|
protected override Color4 ColourAt(float position)
|
||||||
{
|
{
|
||||||
if (position >= GlowPortion)
|
if (position >= GlowPortion)
|
||||||
return BarColour;
|
return BarColour;
|
||||||
|
|
||||||
return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuint);
|
return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -15,6 +16,8 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable
|
public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable
|
||||||
{
|
{
|
||||||
|
private ArgonScoreTextComponent scoreText = null!;
|
||||||
|
|
||||||
protected override double RollingDuration => 500;
|
protected override double RollingDuration => 500;
|
||||||
protected override Easing RollingEasing => Easing.OutQuint;
|
protected override Easing RollingEasing => Easing.OutQuint;
|
||||||
|
|
||||||
@ -33,13 +36,42 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString();
|
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString();
|
||||||
|
|
||||||
protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
|
protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
|
||||||
{
|
{
|
||||||
RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits },
|
|
||||||
WireframeOpacity = { BindTarget = WireframeOpacity },
|
WireframeOpacity = { BindTarget = WireframeOpacity },
|
||||||
ShowLabel = { BindTarget = ShowLabel },
|
ShowLabel = { BindTarget = ShowLabel },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ArgonScoreCounter()
|
||||||
|
{
|
||||||
|
RequiredDisplayDigits.BindValueChanged(_ => updateWireframe());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long DisplayedCount
|
||||||
|
{
|
||||||
|
get => base.DisplayedCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.DisplayedCount = value;
|
||||||
|
updateWireframe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWireframe()
|
||||||
|
{
|
||||||
|
scoreText.RequiredDisplayDigits.Value =
|
||||||
|
Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDigitsRequiredForDisplayCount()
|
||||||
|
{
|
||||||
|
int digitsRequired = 1;
|
||||||
|
long c = DisplayedCount;
|
||||||
|
while ((c /= 10) > 0)
|
||||||
|
digitsRequired++;
|
||||||
|
return digitsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
private partial class ArgonScoreTextComponent : ArgonCounterTextComponent
|
private partial class ArgonScoreTextComponent : ArgonCounterTextComponent
|
||||||
{
|
{
|
||||||
public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null)
|
public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null)
|
||||||
|
@ -11,11 +11,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public bool UsesFixedAnchor { get; set; }
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
protected ComboCounter()
|
|
||||||
{
|
|
||||||
Current.Value = DisplayedCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double GetProportionalDuration(int currentValue, int newValue)
|
protected override double GetProportionalDuration(int currentValue, int newValue)
|
||||||
{
|
{
|
||||||
return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f;
|
return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f;
|
||||||
|
@ -100,11 +100,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha);
|
double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha);
|
||||||
|
|
||||||
boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f);
|
boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f);
|
||||||
|
|
||||||
base.Update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -30,11 +31,13 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public Bindable<double> Current { get; } = new BindableDouble
|
public Bindable<double> Current { get; } = new BindableDouble
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 1
|
MaxValue = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
private BindableNumber<double> health = null!;
|
private BindableNumber<double> health = null!;
|
||||||
|
|
||||||
|
protected bool InitialAnimationPlaying => initialIncrease != null;
|
||||||
|
|
||||||
private ScheduledDelegate? initialIncrease;
|
private ScheduledDelegate? initialIncrease;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -56,13 +59,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
// Don't bind directly so we can animate the startup procedure.
|
// Don't bind directly so we can animate the startup procedure.
|
||||||
health = HealthProcessor.Health.GetBoundCopy();
|
health = HealthProcessor.Health.GetBoundCopy();
|
||||||
health.BindValueChanged(h =>
|
|
||||||
{
|
|
||||||
if (initialIncrease != null)
|
|
||||||
FinishInitialAnimation(h.OldValue);
|
|
||||||
|
|
||||||
Current.Value = h.NewValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hudOverlay != null)
|
if (hudOverlay != null)
|
||||||
showHealthBar.BindTo(hudOverlay.ShowHealthBar);
|
showHealthBar.BindTo(hudOverlay.ShowHealthBar);
|
||||||
@ -70,12 +66,42 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
// this probably shouldn't be operating on `this.`
|
// this probably shouldn't be operating on `this.`
|
||||||
showHealthBar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
|
showHealthBar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
|
||||||
|
|
||||||
|
initialHealthValue = health.Value;
|
||||||
|
|
||||||
if (PlayInitialIncreaseAnimation)
|
if (PlayInitialIncreaseAnimation)
|
||||||
startInitialAnimation();
|
startInitialAnimation();
|
||||||
else
|
else
|
||||||
Current.Value = health.Value;
|
Current.Value = health.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double lastValue;
|
||||||
|
private double initialHealthValue;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!InitialAnimationPlaying || health.Value != initialHealthValue)
|
||||||
|
{
|
||||||
|
Current.Value = health.Value;
|
||||||
|
|
||||||
|
if (initialIncrease != null)
|
||||||
|
FinishInitialAnimation(Current.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health changes every frame in draining situations.
|
||||||
|
// Manually handle value changes to avoid bindable event flow overhead.
|
||||||
|
if (!Precision.AlmostEquals(lastValue, Current.Value, 0.001f))
|
||||||
|
{
|
||||||
|
HealthChanged(Current.Value > lastValue);
|
||||||
|
lastValue = Current.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HealthChanged(bool increase)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private void startInitialAnimation()
|
private void startInitialAnimation()
|
||||||
{
|
{
|
||||||
if (Current.Value >= health.Value)
|
if (Current.Value >= health.Value)
|
||||||
|
@ -40,6 +40,12 @@ namespace osu.Game.Screens.Play
|
|||||||
Precision = 0.1,
|
Precision = 0.1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the audio playback rate should be validated.
|
||||||
|
/// Mostly disabled for tests.
|
||||||
|
/// </summary>
|
||||||
|
internal bool ShouldValidatePlaybackRate { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the audio playback is within acceptable ranges.
|
/// Whether the audio playback is within acceptable ranges.
|
||||||
/// Will become false if audio playback is not going as expected.
|
/// Will become false if audio playback is not going as expected.
|
||||||
@ -223,6 +229,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void checkPlaybackValidity()
|
private void checkPlaybackValidity()
|
||||||
{
|
{
|
||||||
|
if (!ShouldValidatePlaybackRate)
|
||||||
|
return;
|
||||||
|
|
||||||
if (GameplayClock.IsRunning)
|
if (GameplayClock.IsRunning)
|
||||||
{
|
{
|
||||||
elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime;
|
elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user