1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-22 09:09:54 +08:00

Compare commits

..

63 Commits

54 changed files with 1041 additions and 93 deletions
+739
View 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
}
}
+5
View File
@@ -30,6 +30,11 @@ namespace osu.Desktop
[STAThread]
public static void Main(string[] args)
{
// 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;
// run Squirrel first, as the app may exit after these run
if (OperatingSystem.IsWindows())
{
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Mod = new CatchModHidden(),
PassCondition = () => Player.Results.Count > 0
&& Player.ChildrenOfType<DrawableJuiceStream>().Single().Alpha > 0
&& Player.ChildrenOfType<DrawableFruit>().Last().Alpha > 0
&& Player.ChildrenOfType<DrawableFruit>().First().Alpha > 0
});
}
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
AddInternal(bananaContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both });
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
AddInternal(dropletContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both, });
}
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;
}
}
}
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertHeadJudgement(HitResult.Meh);
assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -467,13 +467,13 @@ namespace osu.Game.Rulesets.Osu.Tests
private void assertHeadMissTailTracked()
{
AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit));
AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.SliderTailHit));
AddAssert("Slider head missed", () => judgementResults.First().IsHit, () => Is.False);
}
private void assertMidSliderJudgements()
{
AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit));
AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.SliderTailHit));
}
private void assertMidSliderJudgementFail()
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
});
assertHeadJudgement(HitResult.Ok);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertTickJudgement(1, HitResult.LargeTickHit);
assertTickJudgement(2, HitResult.LargeTickHit);
assertTickJudgement(3, HitResult.LargeTickHit);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertHeadJudgement(HitResult.Meh);
assertAllTickJudgements(HitResult.LargeTickHit);
assertRepeatJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertHeadJudgement(HitResult.Meh);
assertRepeatJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertAllTickJudgements(HitResult.LargeTickMiss);
// This particular test actually starts tracking the slider just before the end, so the tail should be hit because of its leniency.
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -276,7 +276,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertHeadJudgement(HitResult.Meh);
assertTickJudgement(0, HitResult.LargeTickMiss);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -307,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.Tests
assertHeadJudgement(HitResult.Meh);
assertTickJudgement(0, HitResult.LargeTickMiss);
assertTickJudgement(1, HitResult.LargeTickMiss);
assertTailJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.SliderTailHit);
assertSliderJudgement(HitResult.IgnoreHit);
}
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class TailJudgement : SliderEndJudgement
{
public override HitResult MaxResult => HitResult.LargeTickHit;
public override HitResult MaxResult => HitResult.SliderTailHit;
public override HitResult MinResult => HitResult.IgnoreMiss;
}
}
+2
View File
@@ -277,6 +277,7 @@ namespace osu.Game.Rulesets.Osu
HitResult.LargeTickHit,
HitResult.SmallTickHit,
HitResult.SliderTailHit,
HitResult.SmallBonus,
HitResult.LargeBonus,
};
@@ -289,6 +290,7 @@ namespace osu.Game.Rulesets.Osu
case HitResult.LargeTickHit:
return "slider tick";
case HitResult.SliderTailHit:
case HitResult.SmallTickHit:
return "slider end";
@@ -91,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
// When classic slider mechanics are enabled, this result comes from the tail.
return 0.02;
case HitResult.SliderTailHit:
case HitResult.LargeTickHit:
switch (result.HitObject)
{
@@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
increase = 0.02;
break;
case HitResult.SliderTailHit:
case HitResult.LargeTickHit:
// This result comes from either a slider tick or repeat.
increase = hitObject is SliderTick ? 0.015 : 0.02;
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
RelativeSizeAxes = Axes.Both,
InnerRadius = arc_radius,
RoundedCaps = true,
GlowColour = new Color4(171, 255, 255, 255)
GlowColour = new Color4(171, 255, 255, 180)
}
};
}
+18 -1
View File
@@ -112,7 +112,7 @@ namespace osu.Game.Tests.Chat
});
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 /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);
}
[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)
{
var message = new Message(++currentMessageId)
+30
View File
@@ -310,6 +310,26 @@ namespace osu.Game.Tests.Mods
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
{
}
@@ -339,6 +359,16 @@ namespace osu.Game.Tests.Mods
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
{
}
@@ -84,6 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 493_652)]
[TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)]
[TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 326_963)]
[TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 326_963)]
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)]
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)]
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)]
@@ -96,6 +97,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 49_365)]
[TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)]
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 32_696)]
[TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 32_696)]
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)]
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)]
public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore)
@@ -167,6 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.Perfect, HitResult.Miss)]
[TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)]
[TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)]
[TestCase(HitResult.SliderTailHit, HitResult.LargeTickMiss)]
[TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)]
[TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)]
public void TestMinResults(HitResult hitResult, HitResult expectedMinResult)
@@ -187,6 +190,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.SmallTickHit, false)]
[TestCase(HitResult.LargeTickMiss, true)]
[TestCase(HitResult.LargeTickHit, true)]
[TestCase(HitResult.SliderTailHit, true)]
[TestCase(HitResult.SmallBonus, false)]
[TestCase(HitResult.LargeBonus, false)]
public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue)
@@ -207,6 +211,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.SmallTickHit, true)]
[TestCase(HitResult.LargeTickMiss, true)]
[TestCase(HitResult.LargeTickHit, true)]
[TestCase(HitResult.SliderTailHit, true)]
[TestCase(HitResult.SmallBonus, false)]
[TestCase(HitResult.LargeBonus, false)]
public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue)
@@ -227,6 +232,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.SmallTickHit, false)]
[TestCase(HitResult.LargeTickMiss, false)]
[TestCase(HitResult.LargeTickHit, false)]
[TestCase(HitResult.SliderTailHit, false)]
[TestCase(HitResult.SmallBonus, true)]
[TestCase(HitResult.LargeBonus, true)]
public void TestIsBonus(HitResult hitResult, bool expectedReturnValue)
@@ -247,6 +253,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.SmallTickHit, true)]
[TestCase(HitResult.LargeTickMiss, false)]
[TestCase(HitResult.LargeTickHit, true)]
[TestCase(HitResult.SliderTailHit, true)]
[TestCase(HitResult.SmallBonus, true)]
[TestCase(HitResult.LargeBonus, true)]
public void TestIsHit(HitResult hitResult, bool expectedReturnValue)
@@ -267,6 +274,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestCase(HitResult.SmallTickHit, true)]
[TestCase(HitResult.LargeTickMiss, true)]
[TestCase(HitResult.LargeTickHit, true)]
[TestCase(HitResult.SliderTailHit, true)]
[TestCase(HitResult.SmallBonus, true)]
[TestCase(HitResult.LargeBonus, true)]
public void TestIsScorable(HitResult hitResult, bool expectedReturnValue)
@@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Editing
if (sameRuleset)
{
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.
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Beatmaps;
@@ -359,6 +360,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AllowImportCompletion = new SemaphoreSlim(1);
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
{
ShouldValidatePlaybackRate = false,
};
protected override async Task ImportScore(Score score)
{
ScoreImportStarted = true;
@@ -29,6 +29,7 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
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);
AddAssert("check is fail", () =>
{
var scoreInfo = ((ResultsScreen)multiplayerComponents.CurrentScreen).Score;
return !scoreInfo.Passed && scoreInfo.Rank == ScoreRank.F;
});
}
[Test]
@@ -799,11 +799,7 @@ namespace osu.Game.Tests.Visual.Navigation
});
});
AddStep("attempt exit", () =>
{
for (int i = 0; i < 2; ++i)
Game.ScreenStack.CurrentScreen.Exit();
});
AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2);
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
}
@@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Online
{
var cardContainer = this.ChildrenOfType<ReverseChildIDFillFlowContainer<BeatmapCard>>().Single().Parent;
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));
});
}
@@ -275,7 +275,7 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed
AddStep("set content", () =>
{
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!
This is a line before the fenced code block:
@@ -9,6 +9,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select;
using osu.Game.Utils;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private bool assertModsMultiplier(IEnumerable<Mod> mods)
{
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;
}
@@ -20,6 +20,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
using osu.Game.Skinning;
using osu.Game.Users;
namespace osu.Game.Configuration
{
@@ -193,6 +194,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
}
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
@@ -420,5 +422,6 @@ namespace osu.Game.Configuration
EditorShowSpeedChanges,
TouchDisableGameplayTaps,
ModSelectTextSearchStartsActive,
UserOnlineStatus,
}
}
@@ -316,7 +316,7 @@ namespace osu.Game.Database
// 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.
double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0
? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore)
? Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore)
: 0;
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
+13 -2
View File
@@ -62,6 +62,9 @@ namespace osu.Game.Online.API
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
@@ -85,12 +88,20 @@ namespace osu.Game.Online.API
authentication.TokenString = config.Get<string>(OsuSetting.Token);
authentication.Token.ValueChanged += onTokenChanged;
config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
localUser.BindValueChanged(u =>
{
u.OldValue?.Activity.UnbindFrom(activity);
u.NewValue.Activity.BindTo(activity);
if (u.OldValue != null)
localUserStatus.UnbindFrom(u.OldValue.Status);
localUserStatus.BindTo(u.NewValue.Status);
}, true);
localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue);
var thread = new Thread(run)
{
Name = "APIAccess",
@@ -200,6 +211,7 @@ namespace osu.Game.Online.API
setLocalUser(new APIUser
{
Username = ProvidedUsername,
Status = { Value = configStatus.Value ?? UserStatus.Online }
});
}
@@ -246,8 +258,7 @@ namespace osu.Game.Online.API
};
userReq.Success += user =>
{
// todo: save/pull from settings
user.Status.Value = UserStatus.Online;
user.Status.Value = configStatus.Value ?? UserStatus.Online;
setLocalUser(user);
+1 -1
View File
@@ -247,7 +247,7 @@ namespace osu.Game.Online.Chat
string command = parameters[0];
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
switch (command)
switch (command.ToLowerInvariant())
{
case "np":
AddInternal(new NowPlayingCommand(target));
@@ -23,7 +23,6 @@ namespace osu.Game.Online.Metadata
public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence;
private readonly BindableBool isWatchingUserPresence = new BindableBool();
// ReSharper disable once InconsistentlySynchronizedField
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
@@ -192,7 +191,7 @@ namespace osu.Game.Online.Metadata
{
Schedule(() =>
{
if (presence != null)
if (presence?.Status != null)
userStates[userId] = presence.Value;
else
userStates.Remove(userId);
+2 -2
View File
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Box flashLayer;
private Sample flashSample = null!;
private Sample? flashSample;
private readonly Container content;
private readonly Container ring;
@@ -267,7 +267,7 @@ namespace osu.Game.Overlays.Dialog
flashLayer.FadeInFromZero(80, Easing.OutQuint)
.Then()
.FadeOutFromOne(1500, Easing.OutQuint);
flashSample.Play();
flashSample?.Play();
}
protected override bool OnKeyDown(KeyDownEvent e)
+20
View File
@@ -143,6 +143,8 @@ namespace osu.Game.Overlays.Login
panel.Status.BindTo(api.LocalUser.Value.Status);
panel.Activity.BindTo(api.LocalUser.Value.Activity);
panel.Status.BindValueChanged(_ => updateDropdownCurrent(), true);
dropdown.Current.BindValueChanged(action =>
{
switch (action.NewValue)
@@ -174,6 +176,24 @@ namespace osu.Game.Overlays.Login
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;
protected override bool OnClick(ClickEvent e) => true;
@@ -4,7 +4,6 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -15,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
using osuTK;
namespace osu.Game.Overlays.Mods
@@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Mods
{
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
{
+1 -1
View File
@@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Wiki
{
public partial class WikiHeader : BreadcrumbControlOverlayHeader
{
private const string index_path = "Main_Page";
private const string index_path = "Main_page";
public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex;
+2 -2
View File
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays
{
public partial class WikiOverlay : OnlineOverlay<WikiHeader>
{
private const string index_path = @"main_page";
private const string index_path = "Main_page";
public string CurrentPath => path.Value;
@@ -161,7 +161,7 @@ namespace osu.Game.Overlays
path.Value = "error";
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](Main_page)."));
}
private void showParentPage()
@@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Judgements
return HitResult.SmallTickMiss;
case HitResult.LargeTickHit:
case HitResult.SliderTailHit:
return HitResult.LargeTickMiss;
default:
@@ -104,6 +105,7 @@ namespace osu.Game.Rulesets.Judgements
case HitResult.SmallTickMiss:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.SliderTailHit:
case HitResult.LargeTickHit:
return DEFAULT_MAX_HEALTH_INCREASE;
-3
View File
@@ -56,9 +56,6 @@ namespace osu.Game.Rulesets.Mods
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
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)
-2
View File
@@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mods
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)
+15
View File
@@ -139,6 +139,13 @@ namespace osu.Game.Rulesets.Scoring
[Order(15)]
ComboBreak,
/// <summary>
/// A special judgement similar to <see cref="LargeTickHit"/> that's used to increase the valuation of the final tick of a slider.
/// </summary>
[EnumMember(Value = "slider_tail_hit")]
[Order(16)]
SliderTailHit,
/// <summary>
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
///
@@ -188,6 +195,7 @@ namespace osu.Game.Rulesets.Scoring
case HitResult.LargeTickMiss:
case HitResult.LegacyComboIncrease:
case HitResult.ComboBreak:
case HitResult.SliderTailHit:
return true;
default:
@@ -246,6 +254,7 @@ namespace osu.Game.Rulesets.Scoring
case HitResult.LargeTickMiss:
case HitResult.SmallTickHit:
case HitResult.SmallTickMiss:
case HitResult.SliderTailHit:
return true;
default:
@@ -329,6 +338,9 @@ namespace osu.Game.Rulesets.Scoring
case HitResult.ComboBreak:
return true;
case HitResult.SliderTailHit:
return true;
default:
// Note that IgnoreHit and IgnoreMiss are excluded as they do not affect score.
return result >= HitResult.Miss && result < HitResult.IgnoreMiss;
@@ -383,6 +395,9 @@ namespace osu.Game.Rulesets.Scoring
if (minResult == HitResult.IgnoreMiss)
return;
if (maxResult == HitResult.SliderTailHit && minResult != HitResult.LargeTickMiss)
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement.");
if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss)
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement.");
+14 -6
View File
@@ -86,7 +86,9 @@ namespace osu.Game.Rulesets.Scoring
/// <summary>
/// The current rank.
/// </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>
/// The highest combo achieved by this score.
@@ -186,9 +188,13 @@ namespace osu.Game.Rulesets.Scoring
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
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>())
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
};
Mods.ValueChanged += mods =>
@@ -322,6 +328,9 @@ namespace osu.Game.Rulesets.Scoring
case HitResult.LargeTickHit:
return 30;
case HitResult.SliderTailHit:
return 150;
case HitResult.Meh:
return 50;
@@ -408,8 +417,7 @@ namespace osu.Game.Rulesets.Scoring
TotalScore.Value = 0;
Accuracy.Value = 1;
Combo.Value = 0;
Rank.Disabled = false;
Rank.Value = ScoreRank.X;
rank.Value = ScoreRank.X;
HighestCombo.Value = 0;
}
@@ -445,7 +453,7 @@ namespace osu.Game.Rulesets.Scoring
return;
score.Passed = false;
Rank.Value = ScoreRank.F;
rank.Value = ScoreRank.F;
PopulateScore(score);
}
+1
View File
@@ -350,6 +350,7 @@ namespace osu.Game.Scoring
{
case HitResult.SmallTickHit:
case HitResult.LargeTickHit:
case HitResult.SliderTailHit:
case HitResult.LargeBonus:
case HitResult.SmallBonus:
if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0)
@@ -62,6 +62,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button != MouseButton.Left)
return false;
if (rotationHandler == null) return false;
rotationHandler.Begin();
@@ -224,7 +224,7 @@ namespace osu.Game.Screens.Edit.Timing
row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1;
row.WaveformOffsetTo(-offset, animated);
row.WaveformScale = new Vector2(scale, 1);
row.BeatIndex = (int)Math.Floor(index);
row.BeatIndex = (int)Math.Round(index);
index++;
}
+8 -1
View File
@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -25,6 +26,7 @@ using osu.Game.Input.Bindings;
using osu.Game.IO;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds;
@@ -390,7 +392,12 @@ namespace osu.Game.Screens.Menu
if (requiresConfirmation)
{
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
exitDialog.PerformOkAction();
{
if (exitDialog.Buttons.OfType<PopupDialogOkButton>().FirstOrDefault() != null)
exitDialog.PerformOkAction();
else
exitDialog.Flash();
}
else
{
dialogOverlay.Push(new ConfirmExitDialog(() =>
@@ -349,6 +349,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
addItemButton.Alpha = localUserCanAddItem ? 1 : 0;
Scheduler.AddOnce(UpdateMods);
Activity.Value = new UserActivity.InLobby(Room);
}
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;
// Disallow fails in multiplayer for now.
protected override bool CheckModsAllowFailure() => false;
protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
[Resolved]
@@ -55,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
AllowPause = false,
AllowRestart = false,
AllowFailAnimation = false,
AllowSkipping = room.AutoSkip.Value,
AutomaticallySkipIntro = room.AutoSkip.Value,
AlwaysShowLeaderboard = true,
+3 -1
View File
@@ -4,12 +4,14 @@
#nullable disable
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.Break;
namespace osu.Game.Screens.Play
@@ -113,7 +115,7 @@ namespace osu.Game.Screens.Play
if (scoreProcessor != null)
{
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
((IBindable<ScoreRank>)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank);
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play
public bool HasPassed { get; set; }
/// <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>
public bool HasFailed { get; set; }
@@ -40,6 +40,12 @@ namespace osu.Game.Screens.Play
Precision = 0.1,
};
/// <summary>
/// Whether the audio playback rate should be validated.
/// Mostly disabled for tests.
/// </summary>
internal bool ShouldValidatePlaybackRate { get; init; } = true;
/// <summary>
/// Whether the audio playback is within acceptable ranges.
/// Will become false if audio playback is not going as expected.
@@ -223,6 +229,9 @@ namespace osu.Game.Screens.Play
private void checkPlaybackValidity()
{
if (!ShouldValidatePlaybackRate)
return;
if (GameplayClock.IsRunning)
{
elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime;
+32 -27
View File
@@ -735,7 +735,7 @@ namespace osu.Game.Screens.Play
}
// Only show the completion screen if the player hasn't failed
if (HealthProcessor.HasFailed)
if (GameplayState.HasFailed)
return;
GameplayState.HasPassed = true;
@@ -801,8 +801,6 @@ namespace osu.Game.Screens.Play
// This player instance may already be in the process of exiting.
return;
Debug.Assert(ScoreProcessor.Rank.Value != ScoreRank.F);
this.Push(CreateResults(prepareScoreForDisplayTask.GetResultSafely()));
}, Time.Current + delay, 50);
@@ -924,37 +922,44 @@ namespace osu.Game.Screens.Play
if (!CheckModsAllowFailure())
return false;
Debug.Assert(!GameplayState.HasFailed);
Debug.Assert(!GameplayState.HasPassed);
Debug.Assert(!GameplayState.HasQuit);
if (Configuration.AllowFailAnimation)
{
Debug.Assert(!GameplayState.HasFailed);
Debug.Assert(!GameplayState.HasPassed);
Debug.Assert(!GameplayState.HasQuit);
GameplayState.HasFailed = true;
GameplayState.HasFailed = true;
updateGameplayState();
updateGameplayState();
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
// In such cases we want the fail state to precede a user triggered pause.
if (PauseOverlay.State.Value == Visibility.Visible)
PauseOverlay.Hide();
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
// In such cases we want the fail state to precede a user triggered pause.
if (PauseOverlay.State.Value == Visibility.Visible)
PauseOverlay.Hide();
failAnimationContainer.Start();
failAnimationContainer.Start();
// Failures can be triggered either by a judgement, or by a mod.
//
// For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received
// the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above).
//
// A schedule here ensures that any lingering judgements from the current frame are applied before we
// finalise the score as "failed".
Schedule(() =>
// Failures can be triggered either by a judgement, or by a mod.
//
// For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received
// the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above).
//
// A schedule here ensures that any lingering judgements from the current frame are applied before we
// finalise the score as "failed".
Schedule(() =>
{
ScoreProcessor.FailScore(Score.ScoreInfo);
OnFail();
if (GameplayState.Mods.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail))
Restart(true);
});
}
else
{
ScoreProcessor.FailScore(Score.ScoreInfo);
OnFail();
if (GameplayState.Mods.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail))
Restart(true);
});
}
return true;
}
@@ -15,6 +15,12 @@ namespace osu.Game.Screens.Play
/// </summary>
public bool ShowResults { get; set; } = true;
/// <summary>
/// Whether the fail animation / screen should be triggered on failing.
/// If false, the score will still be marked as failed but gameplay will continue.
/// </summary>
public bool AllowFailAnimation { get; set; } = true;
/// <summary>
/// Whether the player should be allowed to trigger a restart.
/// </summary>
+8 -4
View File
@@ -41,6 +41,7 @@ namespace osu.Game.Screens.Play
[Resolved]
private SessionStatics statics { get; set; }
private readonly object scoreSubmissionLock = new object();
private TaskCompletionSource<bool> scoreSubmissionSource;
protected SubmittingPlayer(PlayerConfiguration configuration = null)
@@ -228,16 +229,19 @@ namespace osu.Game.Screens.Play
return Task.CompletedTask;
}
if (scoreSubmissionSource != null)
return scoreSubmissionSource.Task;
lock (scoreSubmissionLock)
{
if (scoreSubmissionSource != null)
return scoreSubmissionSource.Task;
scoreSubmissionSource = new TaskCompletionSource<bool>();
}
// if the user never hit anything, this score should not be counted in any way.
if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0))
return Task.CompletedTask;
Logger.Log($"Beginning score submission (token:{token.Value})...");
scoreSubmissionSource = new TaskCompletionSource<bool>();
var request = CreateSubmissionRequest(score, token.Value);
request.Success += s =>
+4 -4
View File
@@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osu.Game.Input.Bindings;
using osu.Game.Utils;
namespace osu.Game.Screens.Select
{
@@ -87,12 +88,11 @@ namespace osu.Game.Screens.Select
private void updateMultiplierText() => Schedule(() =>
{
double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1;
MultiplierText.Text = multiplier == 1 ? string.Empty : ModUtils.FormatScoreMultiplier(multiplier);
MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
if (multiplier > 1.0)
if (multiplier > 1)
MultiplierText.FadeColour(highMultiplierColour, 200);
else if (multiplier < 1.0)
else if (multiplier < 1)
MultiplierText.FadeColour(lowMultiplierColour, 200);
else
MultiplierText.FadeColour(Color4.White, 200);
@@ -132,8 +132,8 @@ namespace osu.Game.Tests.Beatmaps
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => userSkinResourceStore;
public new IResourceStore<byte[]> Resources => base.Resources;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
RealmAccess IStorageResourceProvider.RealmAccess => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null!;
RealmAccess IStorageResourceProvider.RealmAccess => null!;
#endregion
+18
View File
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -226,5 +228,21 @@ namespace osu.Game.Utils
return proposedWereValid;
}
/// <summary>
/// Given a value of a score multiplier, returns a string version with special handling for a value near 1.00x.
/// </summary>
/// <param name="scoreMultiplier">The value of the score multiplier.</param>
/// <returns>A formatted score multiplier with a trailing "x" symbol</returns>
public static LocalisableString FormatScoreMultiplier(double scoreMultiplier)
{
// Round multiplier values away from 1.00x to two significant digits.
if (scoreMultiplier > 1)
scoreMultiplier = Math.Ceiling(Math.Round(scoreMultiplier * 100, 12)) / 100;
else
scoreMultiplier = Math.Floor(Math.Round(scoreMultiplier * 100, 12)) / 100;
return scoreMultiplier.ToLocalisableString("0.00x");
}
}
}