mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 18:29:58 +08:00
Compare commits
372 Commits
@@ -11,6 +11,10 @@ body:
|
||||
- Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0).
|
||||
- And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful.
|
||||
|
||||
# ATTENTION LINUX USERS
|
||||
|
||||
If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software.
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
@@ -38,7 +42,7 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: The version you encountered this bug on. This is shown at the bottom of the main menu and also at the end of the settings screen.
|
||||
description: The version you encountered this bug on. This is shown at the end of the settings overlay.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1219.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1227.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -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,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())
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneCatchModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneCatchModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public partial class TestSceneOutOfBoundsObjects : TestSceneCatchPlayer
|
||||
{
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[Test]
|
||||
public void TestNoOutOfBoundsObjects()
|
||||
{
|
||||
bool anyObjectOutOfBounds = false;
|
||||
|
||||
AddStep("reset flag", () => anyObjectOutOfBounds = false);
|
||||
|
||||
AddUntilStep("check for out-of-bounds objects",
|
||||
() =>
|
||||
{
|
||||
anyObjectOutOfBounds |= Player.ChildrenOfType<DrawableCatchHitObject>().Any(dho => dho.X < 0 || dho.X > CatchPlayfield.WIDTH);
|
||||
return Player.ScoreProcessor.HasCompleted.Value;
|
||||
});
|
||||
|
||||
AddAssert("no out of bound objects found", () => !anyObjectOutOfBounds);
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit { StartTime = 1000, X = -50 },
|
||||
new Fruit { StartTime = 1200, X = CatchPlayfield.WIDTH + 50 },
|
||||
new JuiceStream
|
||||
{
|
||||
StartTime = 1500,
|
||||
X = 10,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(-200, 0)
|
||||
})
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
StartTime = 3000,
|
||||
X = CatchPlayfield.WIDTH - 10,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(200, 0)
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor();
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
@@ -134,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||
}
|
||||
else
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
private double placementStartTime;
|
||||
private double placementEndTime;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public BananaShowerPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public JuiceStreamPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
private void updateXPosition(ValueChangedEvent<float> _)
|
||||
{
|
||||
X = OriginalXBindable.Value + XOffsetBindable.Value;
|
||||
// same as `CatchHitObject.EffectiveX`.
|
||||
// not using that property directly to support scenarios where `HitObject` may not necessarily be present
|
||||
// for this pooled drawable.
|
||||
X = Math.Clamp(OriginalXBindable.Value + XOffsetBindable.Value, 0, CatchPlayfield.WIDTH);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
||||
}
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||
|
||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@@ -25,8 +26,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData
|
||||
{
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
||||
&& Player.ScoreProcessor.Accuracy.Value == 1
|
||||
&& Player.ScoreProcessor.TotalScore.Value == 1_000_000,
|
||||
&& Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01)
|
||||
&& Player.ScoreProcessor.TotalScore.Value == 946_049,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
@@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
Mod = doubleTime,
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
||||
&& Player.ScoreProcessor.Accuracy.Value == 1
|
||||
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier),
|
||||
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
// 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneManiaModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
@@ -24,5 +29,52 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
|
||||
|
||||
[Test]
|
||||
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 1000,
|
||||
}
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 3000,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
public TestSceneManiaModSuddenDeath()
|
||||
: base(new ManiaModSuddenDeath())
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 1000,
|
||||
}
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 3000,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -200,12 +200,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
|
||||
assertComboAtJudgement(1, 1);
|
||||
assertComboAtJudgement(0, 1);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
assertComboAtJudgement(2, 0);
|
||||
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
|
||||
assertComboAtJudgement(4, 1);
|
||||
assertComboAtJudgement(1, 0);
|
||||
assertComboAtJudgement(3, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
|
||||
@@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
|
||||
{
|
||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
||||
|
||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||
// make the map harder and is more of a personal preference.
|
||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,10 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
|
||||
{
|
||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
||||
|
||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||
// make the map any harder and is more of a personal preference.
|
||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +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.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModPerfect : ModPerfect
|
||||
{
|
||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
||||
{
|
||||
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
|
||||
return false;
|
||||
|
||||
// Mania allows imperfect "Great" hits without failing.
|
||||
if (result.Judgement.MaxResult == HitResult.Perfect)
|
||||
return result.Type < HitResult.Great;
|
||||
|
||||
return result.Type != result.Judgement.MaxResult;
|
||||
}
|
||||
|
||||
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
private Drawable headPiece;
|
||||
|
||||
private DrawableNotePerfectBonus perfectBonus;
|
||||
|
||||
public DrawableNote()
|
||||
: this(null)
|
||||
{
|
||||
@@ -93,10 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
{
|
||||
perfectBonus.TriggerResult(false);
|
||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -107,16 +100,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
result = GetCappedResult(result);
|
||||
|
||||
perfectBonus.TriggerResult(result == HitResult.Perfect);
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
||||
public override void MissForcefully()
|
||||
{
|
||||
perfectBonus.TriggerResult(false);
|
||||
base.MissForcefully();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some objects in mania may want to limit the max result.
|
||||
/// </summary>
|
||||
@@ -137,32 +123,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableNotePerfectBonus bonus:
|
||||
AddInternal(perfectBonus = bonus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
RemoveInternal(perfectBonus, false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case NotePerfectBonus bonus:
|
||||
return new DrawableNotePerfectBonus(bonus);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
private void updateSnapColour()
|
||||
{
|
||||
if (beatmap == null || HitObject == null) return;
|
||||
|
||||
@@ -1,26 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableNotePerfectBonus : DrawableManiaHitObject<NotePerfectBonus>
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableNotePerfectBonus()
|
||||
: this(null!)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableNotePerfectBonus(NotePerfectBonus hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a judgement result.
|
||||
/// </summary>
|
||||
/// <param name="hit">Whether this tick was reached.</param>
|
||||
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.Threading;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
|
||||
@@ -13,12 +12,5 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
public class Note : ManiaHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new ManiaJudgement();
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
AddNested(new NotePerfectBonus { StartTime = StartTime });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +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 osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
public class NotePerfectBonus : ManiaHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new NotePerfectBonusJudgement();
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
public class NotePerfectBonusJudgement : ManiaJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.SmallBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,37 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
|
||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||
{
|
||||
return 10000 * comboProgress
|
||||
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||
return 150000 * comboProgress
|
||||
+ 850000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||
+ bonusPortion;
|
||||
}
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||
{
|
||||
return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||
}
|
||||
|
||||
public override int GetBaseScoreForResult(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
return 305;
|
||||
}
|
||||
|
||||
return base.GetBaseScoreForResult(result);
|
||||
}
|
||||
|
||||
private int getBaseComboScoreForResult(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
return 300;
|
||||
}
|
||||
|
||||
return GetBaseScoreForResult(result);
|
||||
}
|
||||
|
||||
private class JudgementOrderComparer : IComparer<HitObject>
|
||||
{
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
|
||||
{
|
||||
private const float judgement_y_position = 160;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
@@ -30,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
Y = 160;
|
||||
Y = judgement_y_position;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -76,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
this.ScaleTo(1.6f);
|
||||
this.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToY(judgement_y_position);
|
||||
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
this.RotateTo(0);
|
||||
|
||||
@@ -109,7 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||
|
||||
RegisterPool<Note, DrawableNote>(10, 50);
|
||||
RegisterPool<NotePerfectBonus, DrawableNotePerfectBonus>(10, 50);
|
||||
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||
|
||||
@@ -19,12 +19,14 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -57,6 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
// Stores the current speed adjustment active in gameplay.
|
||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; }
|
||||
|
||||
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
@@ -104,7 +109,20 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
updateTimeRange();
|
||||
}
|
||||
|
||||
private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
|
||||
private void updateTimeRange()
|
||||
{
|
||||
float hitPosition = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
||||
?? Stage.HIT_TARGET_POSITION;
|
||||
|
||||
const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION;
|
||||
float lengthToHitPosition = 768 - hitPosition;
|
||||
|
||||
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
||||
float scale = lengthToHitPosition / length_to_default_hit_position;
|
||||
|
||||
TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40.
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
// 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneOsuModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
@@ -50,5 +54,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissSliderTail() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
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
|
||||
{
|
||||
public partial class TestSceneOsuModStrictTracking : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestSliderInput() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModStrictTracking(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(0, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(200, 0)),
|
||||
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1751, new Vector2(0, 100)),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
public TestSceneOsuModSuddenDeath()
|
||||
: base(new OsuModSuddenDeath())
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissTail() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestMissTick() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
double trackerRotationTolerance = 0;
|
||||
|
||||
addSeekStep(5000);
|
||||
AddStep("calculate rotation tolerance", () =>
|
||||
{
|
||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||
});
|
||||
AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); });
|
||||
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
|
||||
@@ -133,9 +130,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddAssert("player score matching expected bonus score", () =>
|
||||
{
|
||||
var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor;
|
||||
|
||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||
long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
|
||||
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
|
||||
@@ -5,12 +5,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
@@ -171,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||
}
|
||||
else
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
@@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
||||
.Concat(new[]
|
||||
{
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap })
|
||||
});
|
||||
|
||||
private BindableList<HitObject> selectedHitObjects;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAccuracyChallenge : ModAccuracyChallenge
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModAutopilot : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Autopilot";
|
||||
public override string Acronym => "AP";
|
||||
@@ -29,18 +29,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(ModRelax),
|
||||
typeof(ModFailCondition),
|
||||
typeof(ModNoFail),
|
||||
typeof(ModAutoplay),
|
||||
typeof(OsuModMagnetised),
|
||||
typeof(OsuModRepel),
|
||||
typeof(ModTouchDevice)
|
||||
};
|
||||
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
private OsuInputManager inputManager = null!;
|
||||
|
||||
private List<OsuReplayFrame> replayFrames = null!;
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Depth";
|
||||
public override string Acronym => "DP";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Cube;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "3D. Almost.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray();
|
||||
|
||||
private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -200);
|
||||
private readonly float sliderMinDepth = depthForScale(1.5f); // Depth at which slider's scale will be 1.5f
|
||||
|
||||
[SettingSource("Maximum depth", "How far away objects appear.", 0)]
|
||||
public BindableFloat MaxDepth { get; } = new BindableFloat(100)
|
||||
{
|
||||
Precision = 10,
|
||||
MinValue = 50,
|
||||
MaxValue = 200
|
||||
};
|
||||
|
||||
[SettingSource("Show Approach Circles", "Whether approach circles should be visible.", 1)]
|
||||
public BindableBool ShowApproachCircles { get; } = new BindableBool(true);
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
private void applyTransform(DrawableHitObject drawable, ArmedState state)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
if (!ShowApproachCircles.Value)
|
||||
{
|
||||
var hitObject = (OsuHitObject)drawable.HitObject;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
|
||||
using (circle.BeginAbsoluteSequence(appearTime))
|
||||
circle.ApproachCircle.Hide();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
double time = playfield.Time.Current;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
processHitObject(time, circle);
|
||||
break;
|
||||
|
||||
case DrawableSlider slider:
|
||||
processSlider(time, slider);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processHitObject(double time, DrawableOsuHitObject drawable)
|
||||
{
|
||||
var hitObject = drawable.HitObject;
|
||||
|
||||
// Circles are always moving at the constant speed. They'll fade out before reaching the camera even at extreme conditions (AR 11, max depth).
|
||||
double speed = MaxDepth.Value / hitObject.TimePreempt;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed);
|
||||
|
||||
float scale = scaleForDepth(z);
|
||||
drawable.Position = toPlayfieldPosition(scale, hitObject.StackedPosition);
|
||||
drawable.Scale = new Vector2(scale);
|
||||
}
|
||||
|
||||
private void processSlider(double time, DrawableSlider drawableSlider)
|
||||
{
|
||||
var hitObject = drawableSlider.HitObject;
|
||||
|
||||
double baseSpeed = MaxDepth.Value / hitObject.TimePreempt;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
|
||||
// Allow slider to move at a constant speed if its scale at the end time will be lower than 1.5f
|
||||
float zEnd = MaxDepth.Value - (float)((Math.Max(hitObject.StartTime + hitObject.Duration, appearTime) - appearTime) * baseSpeed);
|
||||
|
||||
if (zEnd > sliderMinDepth)
|
||||
{
|
||||
processHitObject(time, drawableSlider);
|
||||
return;
|
||||
}
|
||||
|
||||
double offsetAfterStartTime = hitObject.Duration + 500;
|
||||
double slowSpeed = Math.Min(-sliderMinDepth / offsetAfterStartTime, baseSpeed);
|
||||
|
||||
double decelerationTime = hitObject.TimePreempt * 0.2;
|
||||
float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5);
|
||||
|
||||
float z;
|
||||
|
||||
if (time < hitObject.StartTime - decelerationTime)
|
||||
{
|
||||
float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime));
|
||||
z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed);
|
||||
}
|
||||
else if (time < hitObject.StartTime)
|
||||
{
|
||||
double timeOffset = time - (hitObject.StartTime - decelerationTime);
|
||||
double deceleration = (slowSpeed - baseSpeed) / decelerationTime;
|
||||
z = decelerationDistance - (float)(baseSpeed * timeOffset + deceleration * timeOffset * timeOffset * 0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
double endTime = hitObject.StartTime + offsetAfterStartTime;
|
||||
z = -(float)((Math.Min(time, endTime) - hitObject.StartTime) * slowSpeed);
|
||||
}
|
||||
|
||||
float scale = scaleForDepth(z);
|
||||
drawableSlider.Position = toPlayfieldPosition(scale, hitObject.StackedPosition);
|
||||
drawableSlider.Scale = new Vector2(scale);
|
||||
}
|
||||
|
||||
private static float scaleForDepth(float depth) => -camera_position.Z / Math.Max(1f, depth - camera_position.Z);
|
||||
|
||||
private static float depthForScale(float scale) => -camera_position.Z / scale + camera_position.Z;
|
||||
|
||||
private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth)
|
||||
{
|
||||
return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray();
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
|
||||
|
||||
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
|
||||
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles), typeof(OsuModDepth) };
|
||||
|
||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModNoFail : ModNoFail
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
protected virtual float EndScale => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) };
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModPerfect : ModPerfect
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
if (!slider.HeadCircle.IsHit)
|
||||
handleHitCircle(slider.HeadCircle);
|
||||
|
||||
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
||||
requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true);
|
||||
break;
|
||||
|
||||
case DrawableSpinner spinner:
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Hit objects run away!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles), typeof(OsuModDepth) };
|
||||
|
||||
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
|
||||
// further implementation will be required for supporting that.
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModDepth) };
|
||||
|
||||
private const int rotate_offset = 360;
|
||||
private const float rotate_starting_width = 2;
|
||||
|
||||
@@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
if (e.NewValue || slider.Judged) return;
|
||||
|
||||
if (slider.Time.Current < slider.HitObject.StartTime)
|
||||
return;
|
||||
|
||||
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
||||
|
||||
if (!tail.Judged)
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(OsuModAutopilot),
|
||||
typeof(OsuModTargetPractice),
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
typeof(OsuModRandom),
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(OsuModStrictTracking),
|
||||
typeof(OsuModSuddenDeath)
|
||||
typeof(OsuModSuddenDeath),
|
||||
typeof(OsuModDepth)
|
||||
}).ToArray();
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(OsuModDepth) }).ToArray();
|
||||
|
||||
private float theta;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "They just won't stay still...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModDepth) };
|
||||
|
||||
private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private Drawable scaleContainer;
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSliderRepeat()
|
||||
: base(null)
|
||||
{
|
||||
|
||||
@@ -24,11 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
/// <summary>
|
||||
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
||||
/// </summary>
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the hit samples only play on successful hits.
|
||||
/// If <c>false</c>, the hit samples will also play on misses.
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private const float default_tick_size = 16;
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
private SkinnableDrawable scaleContainer;
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private const float spinning_sample_initial_frequency = 1.0f;
|
||||
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
||||
|
||||
private SkinnableSound maxBonusSample;
|
||||
private PausableSkinnableSound maxBonusSample;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
|
||||
@@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Looping = true,
|
||||
Frequency = { Value = spinning_sample_initial_frequency }
|
||||
},
|
||||
maxBonusSample = new SkinnableSound
|
||||
maxBonusSample = new PausableSkinnableSound
|
||||
{
|
||||
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME,
|
||||
}
|
||||
@@ -312,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
updateBonusScore();
|
||||
}
|
||||
|
||||
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
|
||||
private static readonly int score_per_tick = new OsuScoreProcessor().GetBaseScoreForResult(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult);
|
||||
|
||||
private void updateBonusScore()
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
updateTracking(isMouseInFollowArea(Tracking));
|
||||
updateTracking(IsMouseInFollowArea(Tracking));
|
||||
}
|
||||
|
||||
public void PostProcessHeadJudgement(DrawableSliderHead head)
|
||||
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (!head.Judged || !head.Result.IsHit)
|
||||
return;
|
||||
|
||||
if (!isMouseInFollowArea(true))
|
||||
if (!IsMouseInFollowArea(true))
|
||||
return;
|
||||
|
||||
Debug.Assert(screenSpaceMousePosition != null);
|
||||
@@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// If all ticks were hit so far, enable tracking the full extent.
|
||||
// If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball.
|
||||
// For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update().
|
||||
updateTracking(allTicksInRange || isMouseInFollowArea(false));
|
||||
updateTracking(allTicksInRange || IsMouseInFollowArea(false));
|
||||
}
|
||||
|
||||
public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset)
|
||||
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// Whether the mouse is currently in the follow area.
|
||||
/// </summary>
|
||||
/// <param name="expanded">Whether to test against the maximum area of the follow circle.</param>
|
||||
private bool isMouseInFollowArea(bool expanded)
|
||||
public bool IsMouseInFollowArea(bool expanded)
|
||||
{
|
||||
if (screenSpaceMousePosition is not Vector2 pos)
|
||||
return false;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
||||
|
||||
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
|
||||
|
||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
|
||||
@@ -209,7 +211,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new ModAdaptiveSpeed(),
|
||||
new OsuModFreezeFrame(),
|
||||
new OsuModBubbles(),
|
||||
new OsuModSynesthesia()
|
||||
new OsuModSynesthesia(),
|
||||
new OsuModDepth()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
@@ -274,6 +277,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.SmallTickHit,
|
||||
HitResult.SliderTailHit,
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
};
|
||||
@@ -286,6 +290,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
case HitResult.LargeTickHit:
|
||||
return "slider tick";
|
||||
|
||||
case HitResult.SliderTailHit:
|
||||
case HitResult.SmallTickHit:
|
||||
return "slider end";
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
public partial class OsuHealthProcessor : DrainingHealthProcessor
|
||||
{
|
||||
private ComboResult currentComboResult = ComboResult.Perfect;
|
||||
|
||||
public OsuHealthProcessor(double drainStartTime, double drainLenience = 0)
|
||||
: base(drainStartTime, drainLenience)
|
||||
{
|
||||
}
|
||||
|
||||
protected override double GetHealthIncreaseFor(JudgementResult result)
|
||||
{
|
||||
if (IsSimulating)
|
||||
return getHealthIncreaseFor(result);
|
||||
|
||||
if (result.HitObject is not IHasComboInformation combo)
|
||||
return getHealthIncreaseFor(result);
|
||||
|
||||
if (combo.NewCombo)
|
||||
currentComboResult = ComboResult.Perfect;
|
||||
|
||||
switch (result.Type)
|
||||
{
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.Ok:
|
||||
setComboResult(ComboResult.Good);
|
||||
break;
|
||||
|
||||
case HitResult.Meh:
|
||||
case HitResult.Miss:
|
||||
setComboResult(ComboResult.None);
|
||||
break;
|
||||
}
|
||||
|
||||
// The slider tail has a special judgement that can't accurately be described above.
|
||||
if (result.HitObject is SliderTailCircle && !result.IsHit)
|
||||
setComboResult(ComboResult.Good);
|
||||
|
||||
if (combo.LastInCombo && result.Type.IsHit())
|
||||
{
|
||||
switch (currentComboResult)
|
||||
{
|
||||
case ComboResult.Perfect:
|
||||
return getHealthIncreaseFor(result) + 0.07;
|
||||
|
||||
case ComboResult.Good:
|
||||
return getHealthIncreaseFor(result) + 0.05;
|
||||
|
||||
default:
|
||||
return getHealthIncreaseFor(result) + 0.03;
|
||||
}
|
||||
}
|
||||
|
||||
return getHealthIncreaseFor(result);
|
||||
|
||||
void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult);
|
||||
}
|
||||
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
currentComboResult = ComboResult.Perfect;
|
||||
}
|
||||
|
||||
private double getHealthIncreaseFor(JudgementResult result)
|
||||
{
|
||||
switch (result.Type)
|
||||
{
|
||||
case HitResult.SmallTickMiss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||
|
||||
case HitResult.LargeTickMiss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||
|
||||
case HitResult.Miss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2);
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
// When classic slider mechanics are enabled, this result comes from the tail.
|
||||
return 0.02;
|
||||
|
||||
case HitResult.SliderTailHit:
|
||||
case HitResult.LargeTickHit:
|
||||
switch (result.HitObject)
|
||||
{
|
||||
case SliderTick:
|
||||
return 0.015;
|
||||
|
||||
case SliderHeadCircle:
|
||||
case SliderTailCircle:
|
||||
case SliderRepeat:
|
||||
return 0.02;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HitResult.Meh:
|
||||
return 0.002;
|
||||
|
||||
case HitResult.Ok:
|
||||
return 0.011;
|
||||
|
||||
case HitResult.Great:
|
||||
return 0.03;
|
||||
|
||||
case HitResult.SmallBonus:
|
||||
return 0.0085;
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return 0.01;
|
||||
}
|
||||
|
||||
return base.GetHealthIncreaseFor(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -62,25 +62,32 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
/// </remarks>
|
||||
public virtual void PlayAnimation()
|
||||
{
|
||||
switch (Result)
|
||||
if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss)
|
||||
{
|
||||
default:
|
||||
JudgementText
|
||||
.FadeInFromZero(300, Easing.OutQuint)
|
||||
.ScaleTo(Vector2.One)
|
||||
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||
break;
|
||||
this.RotateTo(-45);
|
||||
this.ScaleTo(1.8f);
|
||||
this.ScaleTo(1.2f, 100, Easing.In);
|
||||
|
||||
case HitResult.Miss:
|
||||
this.ScaleTo(1.6f);
|
||||
this.ScaleTo(1, 100, Easing.In);
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint);
|
||||
}
|
||||
else if (Result.IsMiss())
|
||||
{
|
||||
this.ScaleTo(1.6f);
|
||||
this.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
this.RotateTo(0);
|
||||
this.RotateTo(40, 800, Easing.InQuint);
|
||||
break;
|
||||
this.RotateTo(0);
|
||||
this.RotateTo(40, 800, Easing.InQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgementText
|
||||
.FadeInFromZero(300, Easing.OutQuint)
|
||||
.ScaleTo(Vector2.One)
|
||||
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
this.FadeOutFromOne(800);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
if (Skin is ArgonProSkin && (resultComponent.Component == HitResult.Great || resultComponent.Component == HitResult.Perfect))
|
||||
return Drawable.Empty();
|
||||
|
||||
return new ArgonJudgementPiece(resultComponent.Component);
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
|
||||
|
||||
if (legacyVersion >= 2.0m)
|
||||
if (legacyVersion > 1.0m)
|
||||
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
|
||||
hitCircleText.FadeOut(legacy_fade_duration / 4);
|
||||
else
|
||||
|
||||
@@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
};
|
||||
|
||||
userCursorScale = config.GetBindable<float>(OsuSetting.GameplayCursorSize);
|
||||
userCursorScale.ValueChanged += _ => calculateCursorScale();
|
||||
userCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale();
|
||||
|
||||
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
|
||||
autoCursorScale.ValueChanged += _ => calculateCursorScale();
|
||||
autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale();
|
||||
|
||||
cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true);
|
||||
}
|
||||
@@ -81,10 +81,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
calculateCursorScale();
|
||||
cursorScale.Value = CalculateCursorScale();
|
||||
}
|
||||
|
||||
private void calculateCursorScale()
|
||||
protected virtual float CalculateCursorScale()
|
||||
{
|
||||
float scale = userCursorScale.Value;
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize);
|
||||
}
|
||||
|
||||
cursorScale.Value = scale;
|
||||
return scale;
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin)
|
||||
|
||||
@@ -20,7 +20,6 @@ using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -66,8 +65,21 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
HitPolicy = new StartTimeOrderedHitPolicy();
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r =>
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case HitResult.Great:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Meh:
|
||||
case HitResult.Miss:
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}))
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
|
||||
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
@@ -170,7 +182,10 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
|
||||
if (!poolDictionary.TryGetValue(result.Type, out var pool))
|
||||
return;
|
||||
|
||||
DrawableOsuJudgement explosion = pool.Get(doj => doj.Apply(result, judgedObject));
|
||||
|
||||
judgementLayer.Add(explosion);
|
||||
|
||||
|
||||
@@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
RelativePositionAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override float CalculateCursorScale()
|
||||
{
|
||||
// Force minimum cursor size so it's easily clickable
|
||||
return Math.Max(1f, base.CalculateCursorScale());
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateColour();
|
||||
|
||||
@@ -10,7 +10,7 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneTaikoModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneTaikoModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset();
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TaikoScoreProcessorTest
|
||||
{
|
||||
[Test]
|
||||
public void TestInaccurateHitScore()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Hit(),
|
||||
new Hit { StartTime = 1000 }
|
||||
}
|
||||
};
|
||||
|
||||
var scoreProcessor = new TaikoScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
// Apply a miss judgement
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Great });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Ok });
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(453745));
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(0.75).Within(0.0001));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -13,11 +12,14 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
private readonly ScoreProcessor scoreProcessor = new TaikoScoreProcessor();
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
@@ -191,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||
}
|
||||
else
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
|
||||
private readonly IHasDuration spanPlacementObject;
|
||||
|
||||
protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
|
||||
|
||||
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
|
||||
@@ -28,11 +28,22 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
{
|
||||
return Judgement.ToNumericResult(result.Type)
|
||||
return GetBaseScoreForResult(result.Type)
|
||||
* Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base))
|
||||
* strongScaleValue(result);
|
||||
}
|
||||
|
||||
public override int GetBaseScoreForResult(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Ok:
|
||||
return 150;
|
||||
}
|
||||
|
||||
return base.GetBaseScoreForResult(result);
|
||||
}
|
||||
|
||||
private double strongScaleValue(JudgementResult result)
|
||||
{
|
||||
if (result.HitObject is StrongNestedHitObject strong)
|
||||
|
||||
@@ -219,6 +219,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
|
||||
};
|
||||
scoreInfo.OnlineID = 123123;
|
||||
scoreInfo.ClientVersion = "2023.1221.0";
|
||||
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
var score = new Score
|
||||
@@ -237,9 +239,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.OnlineID, Is.EqualTo(123123));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -127,8 +127,11 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(30000001)]
|
||||
[TestCase(30000002)]
|
||||
[TestCase(30000003)]
|
||||
[TestCase(30000004)]
|
||||
[TestCase(30000005)]
|
||||
public void TestScoreUpgradeSuccess(int scoreVersion)
|
||||
{
|
||||
ScoreInfo scoreInfo = null!;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
// Apply a judgement
|
||||
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetBaseScoreForResult(HitResult.LargeBonus)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -196,6 +196,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
User = new APIUser { Username = "Test user" },
|
||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
ClientVersion = "12345",
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
};
|
||||
|
||||
@@ -203,6 +204,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
|
||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
||||
Assert.That(imported.ClientVersion, Is.EqualTo(toImport.ClientVersion));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -94,8 +95,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Test]
|
||||
public void TestAddAudioTrack()
|
||||
{
|
||||
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType<Timeline>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddStep("enter setup mode", () => InputManager.Key(Key.F4));
|
||||
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||
AddAssert("switch track to real track", () =>
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osu.Game.Screens.Edit.Timing.RowAttributes;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
@@ -69,6 +70,48 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectedRetainedOverUndo()
|
||||
{
|
||||
AddStep("Select first timing point", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Child.ChildrenOfType<TimingRowAttribute>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 2170);
|
||||
AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 2170);
|
||||
|
||||
AddStep("Adjust offset", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(timingScreen.ChildrenOfType<TimingAdjustButton>().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for offset changed", () =>
|
||||
{
|
||||
return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
});
|
||||
|
||||
AddStep("simulate undo", () =>
|
||||
{
|
||||
var clone = editorBeatmap.ControlPointInfo.DeepClone();
|
||||
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
|
||||
foreach (var group in clone.Groups)
|
||||
{
|
||||
foreach (var cp in group.ControlPoints)
|
||||
editorBeatmap.ControlPointInfo.Add(group.Time, cp);
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("selection retained", () =>
|
||||
{
|
||||
return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTrackingCurrentTimeWhileRunning()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Ranking;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@@ -44,7 +45,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2)
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreFromDifferentBeatmap()
|
||||
{
|
||||
AddStep("Set short reference score", () =>
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10),
|
||||
BeatmapInfo = TestResources.CreateTestBeatmapSetInfo().Beatmaps.First(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -59,7 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10),
|
||||
Mods = new Mod[] { new OsuModRelax() }
|
||||
Mods = new Mod[] { new OsuModRelax() },
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -77,7 +95,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error)
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -105,7 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error)
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private BeatmapSetInfo? importedSet;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase osu { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
@@ -153,6 +156,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||
AddUntilStep("score has correct version", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID)!.ClientVersion), () => Is.EqualTo(osu.Version));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public partial class TestSceneMainMenu : OsuGameTestScene
|
||||
{
|
||||
private SystemTitle systemTitle => Game.ChildrenOfType<SystemTitle>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestSystemTitle()
|
||||
{
|
||||
AddStep("set system title", () => systemTitle.Current.Value = new APISystemTitle
|
||||
{
|
||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
|
||||
});
|
||||
AddAssert("system title not visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("system title visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddStep("set another title", () => systemTitle.Current.Value = new APISystemTitle
|
||||
{
|
||||
Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png",
|
||||
Url = @"https://osu.ppy.sh/community/contests/189",
|
||||
});
|
||||
AddStep("set title with nonexistent image", () => systemTitle.Current.Value = new APISystemTitle
|
||||
{
|
||||
Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2
|
||||
Url = @"https://osu.ppy.sh/community/contests/189",
|
||||
});
|
||||
AddStep("unset system title", () => systemTitle.Current.Value = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-6
@@ -1,19 +1,27 @@
|
||||
// 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.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Menu;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public partial class TestSceneDisclaimer : ScreenTestScene
|
||||
public partial class TestSceneSupporterDisplay : OsuTestScene
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
|
||||
AddStep("create display", () =>
|
||||
{
|
||||
Child = new SupporterDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("toggle support", () =>
|
||||
{
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserWasPlayingBeforeWatchingUserPresence()
|
||||
{
|
||||
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
|
||||
AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence());
|
||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||
|
||||
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
|
||||
}
|
||||
|
||||
internal partial class TestUserLookupCache : UserLookupCache
|
||||
{
|
||||
private static readonly string[] usernames =
|
||||
|
||||
@@ -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:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user